/**
 * Moust Boost — Store styles
 *
 * Everything that draws the EDD store: the compact list row, the single item
 * page, and the search/filter/sort bar.
 *
 * This lives in its own file rather than in style.css because the store list
 * is now rendered by a BLOCK (`acf/mb-store-list`) as well as by the archive
 * template. Block styles must load inside the editor canvas or the block
 * previews as unstyled markup — and style.css is deliberately never loaded
 * there, because its `body`, header and footer rules would leak into the
 * canvas chrome.
 *
 * Loaded on the front end and into the editor via enqueue_block_assets(); see
 * moustboost_enqueue_block_assets() in functions.php.
 *
 * It is NOT merged into blocks.css because most of what follows serves the
 * archive and single templates, which are not blocks at all.
 */

/* =========================================================
   Store — compact list view (EDD downloads)

   One row per item: artwork (with play overlay), title, CTA. Kept low-contrast
   on purpose — a repeating row is not the place for a bright fill.
   ========================================================= */

/* Both selectors below are written as `.base.base--modifier` rather than as a
   bare modifier class.

   A bare `.mb-page__inner--wide` has the same specificity (0,1,0) as the
   `.mb-page__inner` it is meant to override, so which one wins comes down to
   source order — and these rules now live in a different stylesheet from that
   base, one that is enqueued BEFORE style.css. The modifier silently lost, and
   the store archive snapped back to the 48rem article width.

   Doubling the class takes them to (0,2,0), so they beat the base no matter
   which file loads first. */

/* Width is derived from the container primitive rather than restated, so the
   store archive and a store block embedded in a page line up exactly.

   The subtraction is the bridge between two different box models: `.mb-container`
   holds its gutter INSIDE its max-width, whereas `.mb-page` supplies the gutter
   itself and this inner box is pure content. Netting off the gutter makes the
   two content widths identical, and expressing it in the same tokens means they
   stay identical if the container is ever resized. */
.mb-page__inner.mb-page__inner--wide {
  max-width: calc(var(--mb-container, 82.5rem) - var(--mb-container-gutter, 1.5rem) * 2);
}

.mb-page__header.mb-page__header--centered {
  text-align: center;
}

/* The list is a single object rather than loose rows, so it gets one border
   and one radius. `overflow: hidden` is what lets the first and last rows'
   backgrounds clip to that radius — without it the stripes square off the
   corners the border has just rounded. */
.mb-item-list {
  /* Stripe colours live here as custom properties so the nth-child rules and
     the JS-applied stripe classes below can share one definition — two copies
     of a hex value is two chances for them to drift apart. */
  --mb-stripe-odd: #272b3d;
  --mb-stripe-even: #393c4c;

  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 0.0625rem solid var(--mb-border, rgba(255, 255, 255, 0.12));
  border-radius: var(--mb-radius-sm, 0.5rem);
}

.mb-item {
  display: grid;
  /* Artwork sizes itself, then the title block and waveform split the
     remaining space evenly, then the CTA sizes itself.

     Both middle tracks are `1fr` so the slack divides between them rather
     than the waveform taking all of it. The meta column keeps a `10rem` floor
     so a short title can't collapse it, and the waveform keeps a `0` floor so
     it yields first when the row gets tight — a squeezed waveform is still
     legible, a squeezed title is not. */
  grid-template-columns: auto minmax(10rem, 1fr) minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--mb-space-sm, 1rem);
  padding: 0.75rem;
  transition: background-color 0.2s ease;
}

/* Alternating stripes, not dividers.

   Translucent black rather than an opaque colour: shading downward deepens
   the row instead of lifting it toward grey, and an alpha keeps working if
   the section background is ever retinted — a hard-coded hex would have to be
   rechosen by hand.

   ON THE ALPHA VALUES: a literal 3%/6% pair renders as #101726 vs #101725
   over this background — one RGB value apart, i.e. no stripe at all. Black
   overlays compress as the backdrop darkens, so percentages that read as
   "subtle" on white read as "nothing" here. These keep that same 1:2 ratio,
   scaled to where the banding is actually perceptible: about four RGB values
   apart, which resolves into rows when scanned but stays texture at a
   glance. */
.mb-item:nth-child(odd) {
  background: var(--mb-stripe-odd);
}

.mb-item:nth-child(even) {
  background: var(--mb-stripe-even);
}

/* Stripe overrides applied by assets/js/store-filter.js.

   `:nth-child` counts hidden siblings, so the moment the filter bar hides a
   row the zebra collapses into runs of the same shade. These classes are
   computed against VISIBLE rows only and take over when JS is present.

   Specificity is identical to the nth-child pair (0,2,0), so these win purely
   by coming later — which is why they must stay below, not be moved up. */
.mb-item.is-stripe-odd {
  background: var(--mb-stripe-odd);
}

.mb-item.is-stripe-even {
  background: var(--mb-stripe-even);
}

/* Hover lifts rather than deepens, so it reads as a state change against
   either stripe instead of being mistaken for the other one. */
.mb-item:hover,
.mb-item:nth-child(odd):hover,
.mb-item:nth-child(even):hover,
.mb-item.is-stripe-odd:hover,
.mb-item.is-stripe-even:hover {
  background: var(--mb-surface-subtle, rgba(255, 255, 255, 0.05));
}

/* Rows are keyboard focus targets (arrow navigation + Enter/Space actions),
   but a full-width ring reads noisy in this dense list. Focus is expressed as
   a subtle surface lift + local emphasis (title/play control), which is clear
   without drawing bright lines across the row edges. */
.mb-item:focus-visible {
  box-shadow: none;
  background: var(--mb-surface-subtle, rgba(255, 255, 255, 0.05));
}

.mb-item:focus-visible .mb-item__title a {
  color: var(--mb-text-primary, #fff);
}

.mb-item:focus-visible .mb-item__play {
  opacity: 1;
}

/* ---------- Artwork + play control ---------- */

.mb-item__art {
  position: relative;
  flex-shrink: 0;
  width: 3.5rem;
  height: 3.5rem;
  overflow: hidden;
  border-radius: var(--mb-radius-sm, 0.5rem);
  background: var(--mb-bg-elevated, #202538);
}

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

/* The control covers the whole thumbnail rather than sitting in a corner:
   a 56px target is comfortably tappable, a 20px icon is not. */
.mb-item__play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: rgba(10, 10, 10, 0.55);
  border: 0;
  color: var(--mb-text-primary, #fff);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.2s ease;
}

/* Reveal on hover, on keyboard focus, and whenever the track is playing —
   otherwise the only actively-playing row would hide its own pause button the
   moment the pointer moved away. */
.mb-item:hover .mb-item__play,
.mb-item__play:focus-visible,
.mb-item__play[aria-pressed='true'] {
  opacity: 1;
}

/* Touch devices have no hover state to reveal it, so show it permanently. */
@media (hover: none) {
  .mb-item__play {
    opacity: 1;
  }
}

.mb-item__play:focus-visible {
  outline: 0.125rem solid var(--mb-text-primary, #fff);
  outline-offset: -0.125rem;
}

.mb-item__icon,
.mb-item__play svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* Icon swap driven by aria-pressed — see the note in content-download.php. */
.mb-item__icon--pause {
  display: none;
}

.mb-item__play[aria-pressed='true'] .mb-item__icon--play {
  display: none;
}

.mb-item__play[aria-pressed='true'] .mb-item__icon--pause {
  display: block;
}

/* No thumbnail mode.
   The artwork box becomes a circular ring and the play control is always
   visible, since there's no image hover layer to reveal it. */
.mb-item--no-image .mb-item__art {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 9999px;
  overflow: visible;
  border: 0.0625rem solid rgba(255, 255, 255, 0.35);
  background: transparent;
}

.mb-item--no-image .mb-item__play {
  opacity: 1;
  background: transparent;
}

.mb-item--no-image .mb-item__play:focus-visible {
  outline-offset: 0.125rem;
  border-radius: 9999px;
}

/* ---------- Title + tags ---------- */

/* This wrapper, not the title, is now the flexible grid column. */
.mb-item__meta {
  min-width: 0; /* lets the grid column shrink so ellipsis can engage */
}

.mb-item__title {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--mb-text-primary, #fff);
}

.mb-item__title a {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mb-item__title a:hover {
  text-decoration: underline;
  text-underline-offset: 0.1875rem;
}

/* Secondary line under the title: BPM, then tags. */
.mb-item__facets {
  margin: 0.125rem 0 0;
  /* Truncate rather than wrap: a wrapping tag list makes rows different
     heights, which destroys the even rhythm a compact list depends on. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.875rem; /* 14px */
  line-height: 1.4;
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
}

/* The facets line carries three groups: BPM, genres, tags.
 *
 * Group boundaries are marked by a wide gap; members within a group are marked
 * by punctuation. That contrast is what makes the line parseable — if genres
 * and tags shared a separator, "R&B, Hip-hop, Justin Beiber" would read as one
 * flat list and the tag would look like a genre.
 *
 * `:not(:last-child)` keeps a trailing gap from eating into the truncation
 * width when the later groups are absent. */
.mb-item__bpm:not(:last-child),
.mb-item__cats:not(:last-child) {
  margin-right: 1rem;
}

/* BPM is plain text, not a link. */
.mb-item__bpm {
  font-variant-numeric: tabular-nums; /* keeps digits aligned down the column */
}

/* Separators are generated, not authored — see the note in
   content-download.php. They sit on the wrapping span, never on the <a>: a
   pseudo-element on a link belongs to that link's box, so it picked up the
   hover underline and made the gap between items clickable.

   The comma has no left margin so it hugs the preceding word, the way a comma
   does in prose. */
.mb-item__cat + .mb-item__cat::before {
  content: ',';
  margin-right: 0.3125rem;
}

.mb-item__tag + .mb-item__tag::before {
  content: ',';
  margin-right: 0.3125rem;
}

.mb-item__cat a,
.mb-item__tag a {
  color: inherit;
  transition: color 0.2s ease;
}

.mb-item__cat a:hover,
.mb-item__cat a:focus-visible,
.mb-item__tag a:hover,
.mb-item__tag a:focus-visible {
  color: var(--mb-text-primary, #fff);
  text-decoration: underline;
  text-underline-offset: 0.1875rem;
}

/* ---------- Waveform ---------- */

/* The canvas is drawn by assets/js/store-waveform.js, which reads these two
   custom properties. Keeping the colours here rather than in the script means
   the palette stays in one place and follows the tokens.

   Played is white rather than the accent: at this bar density a saturated
   colour reads as a solid block and pulls focus from the title and CTA. The
   two states differ by opacity alone, which is enough to track progress in a
   list row without the waveform becoming the loudest thing on the page. */
.mb-item__wave {
  --mb-wave-idle: rgba(255, 255, 255, 0.22);
  --mb-wave-played: rgba(255, 255, 255, 0.85);

  position: relative;
  height: 2.5rem;
  min-width: 0; /* lets the track shrink instead of forcing the row wider */
  cursor: pointer;
  /* Without this a horizontal drag scrolls the page on touch instead of
     scrubbing, and the pointermove handler never sees the gesture. */
  touch-action: none;
}

.mb-item__wave canvas {
  display: block;
}

/* A row with no preview keeps the column so every CTA stays on the same
   vertical line — an empty cell rather than a collapsed grid. */
.mb-item__wave--empty {
  cursor: default;
}

.mb-item__actions {
  display: flex;
  align-items: center;
  gap: var(--mb-space-2xs, 0.5rem);
}

/* Below this the row can't give a waveform enough width to be readable or
   accurately scrubbable, and the decode isn't worth the bytes on a phone.
   `display: none` also means the canvas is never measured, so the script
   skips drawing it entirely. */
@media (max-width: 64rem) {
  .mb-item {
    grid-template-columns: auto minmax(0, 1fr) auto;
  }

  .mb-item__wave {
    display: none;
  }
}

@media (max-width: 40rem) {
  .mb-item {
    /* Two columns: artwork stays put and spans both rows, title and CTA
       stack beside it. Keeps the row compact rather than letting the CTA
       push the title into a third line. */
    grid-template-columns: auto 1fr;
    gap: 0.5rem var(--mb-space-sm, 1rem);
  }

  .mb-item__art {
    grid-row: span 2;
  }

  .mb-item__title {
    font-size: 1rem;
  }

  .mb-item__actions {
    justify-content: flex-start;
  }
}

/* =========================================================
   Store — single item

   Two columns: the item, and the decision. Everything descriptive sits left,
   everything transactional sits right, so the buy panel is never something
   you have to scroll back up to find.
   ========================================================= */

/* ---------- Breadcrumb ---------- */

.mb-crumbs {
  margin-bottom: var(--mb-space-md, 1.5rem);
  font-size: var(--mb-font-size-sm, 0.875rem);
}

.mb-crumbs__list {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Chevrons are generated rather than authored, for the same reason as the tag
   bullets: a screen reader would otherwise read the separator aloud between
   every crumb, and it would be copied along with selected text.

   `\203A` is a single right-angle quote — lighter than a full chevron glyph at
   this size, and it inherits the tertiary colour so the links read as the
   content and the marks as scaffolding. */
.mb-crumbs__item + .mb-crumbs__item::before {
  content: '\203A';
  margin-right: 0.375rem;
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
}

.mb-crumbs__item {
  display: flex;
  align-items: center;
  min-width: 0;
}

.mb-crumbs__link {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  color: var(--mb-text-secondary, rgba(255, 255, 255, 0.68));
  transition: color 0.2s ease;
}

.mb-crumbs__link:hover,
.mb-crumbs__link:focus-visible {
  color: var(--mb-text-primary, #fff);
}

/* The arrow belongs to the Store crumb only — it's the "get me out of here"
   affordance, and repeating it on every crumb would dilute that. */
.mb-crumbs__icon,
.mb-crumbs__link svg {
  flex-shrink: 0;
  width: var(--mb-button-icon-size, 1rem);
  height: var(--mb-button-icon-size, 1rem);
}

/* The current page is not a link to itself. Truncated because a long title
   would otherwise wrap the trail onto a second line. */
.mb-crumbs__current {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
}

.mb-single {
  display: grid;
  /* 3/9 of a 12-column field. Fractional rather than a fixed cover column so
     the ratio holds at every width instead of the cover keeping one size
     while the buy panel absorbs all the slack.

     `minmax(0, …)` on the main track is what stops a wide price row from
     inflating its column past its share — fr tracks floor at min-content
     otherwise, which silently breaks the ratio. */
  grid-template-columns: minmax(0, 3fr) minmax(0, 9fr);
  gap: var(--mb-space-md, 1.5rem);
  align-items: start;
}

/* Same card treatment as the buy panel, so the two columns read as a matched
   pair rather than a panel floating beside loose content. */
.mb-single__aside {
  min-width: 0; /* lets the column shrink rather than forcing the grid wider */
  padding: var(--mb-space-sm, 1rem);
  background: var(--mb-bg-tertiary, #1c2230);
  border: 0.0625rem solid var(--mb-border, rgba(255, 255, 255, 0.12));
  border-radius: var(--mb-radius-sm, 0.5rem);
}

/* ---------- Cover + play ---------- */

.mb-cover {
  position: relative;
  aspect-ratio: 1;
  overflow: hidden;
  /* Tighter than the card that now contains it. A nested corner should be the
     outer radius minus the padding between them, or the two arcs look
     concentric-but-wrong. */
  border-radius: 0.375rem;
  background: var(--mb-bg-elevated, #202538);
}

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

/* Unlike the list rows, this button is always visible. There's one item on the
   page and playing it is the primary thing to do here — hiding it behind a
   hover would be coy. */
.mb-cover__play {
  inset: auto auto 1rem 1rem;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: var(--mb-accent, #3b82f6);
  opacity: 1;
  transition: transform 0.2s ease, background-color 0.2s ease;
}

.mb-cover__play:hover {
  background: var(--mb-accent-strong, #2563eb);
  transform: scale(1.06);
}

/* The list rules reveal on hover/press; this one is already visible, so those
   would be no-ops — but the hover rule on .mb-item:hover would still apply a
   transition it doesn't need. Pin opacity so nothing fades it back out. */
.mb-cover__play,
.mb-cover__play[aria-pressed='true'] {
  opacity: 1;
}

.mb-cover__play .mb-item__icon,
.mb-cover__play svg {
  width: 1.5rem;
  height: 1.5rem;
}

.mb-single__title {
  margin-top: var(--mb-space-sm, 1rem);
  /* Sized to fit the 3/12 column on one or two lines. The old clamp topped out
     at 2.5rem, which wrapped a two-word title and left the column top-heavy. */
  font-size: clamp(1.25rem, 1.4vw, 1.5rem);
  font-weight: 700;
  line-height: var(--mb-line-height-heading, 1.15);
  letter-spacing: var(--mb-letter-spacing-display, -0.02em);
  color: var(--mb-text-primary, #fff);
}

/* ---------- Facts ---------- */

/* No rules between rows. At this width a divider every ~2.5rem chops a short
   list into fragments and competes with the panel border opposite. Separation
   comes from the gap plus the label/value contrast instead, which is the same
   job done with less ink. */
.mb-facts {
  display: grid;
  gap: var(--mb-space-sm, 1rem);
  margin-top: var(--mb-space-md, 1.5rem);
}

/* Stacked, not two columns: a label column wide enough for "Similar to" would
   leave almost nothing for the value in a 2/12 sidebar. */
.mb-facts__row {
  display: grid;
  gap: 0.125rem;
}

.mb-facts__label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: var(--mb-letter-spacing-meta, 0.03em);
  text-transform: uppercase;
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
}

.mb-facts__value {
  margin: 0;
  font-size: var(--mb-font-size-sm, 0.875rem);
  color: var(--mb-text-secondary, rgba(255, 255, 255, 0.68));
}

.mb-facts__value--num {
  font-variant-numeric: tabular-nums;
}

/* Comma separators are generated so they aren't read aloud between every term
   and aren't copied with the text. On the wrapping rule rather than the link
   itself, or the comma would inherit the hover underline. */
.mb-facts__link + .mb-facts__link::before {
  content: ',';
  margin-right: 0.3125rem;
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
}

.mb-facts__link:hover,
.mb-facts__link:focus-visible {
  color: var(--mb-text-primary, #fff);
  text-decoration: underline;
  text-underline-offset: 0.1875rem;
}

.mb-single__body {
  margin-top: var(--mb-space-md, 1.5rem);
  color: var(--mb-text-secondary, rgba(255, 255, 255, 0.68));
}

.mb-single__body > * + * {
  margin-top: var(--mb-space-sm, 1rem);
}

.mb-single__body a:not(.mb-button) {
  color: #c6d1df;
  text-decoration: underline;
  text-decoration-thickness: 0.0625rem;
  text-underline-offset: 0.1875rem;
}

.mb-single__body a:not(.mb-button):hover,
.mb-single__body a:not(.mb-button):focus-visible {
  color: var(--mb-text-primary, #fff);
}

/* ---------- Buy panel ---------- */

.mb-single__buy {
  /* Follows the cover down a long description instead of stranding the CTA
     at the top. `top` clears the sticky header so it can't tuck underneath. */
  position: sticky;
  top: calc(var(--mb-header-height, 6.375rem) + 1rem);
}

.mb-buy {
  padding: var(--mb-space-md, 1.5rem);
  background: var(--mb-bg-tertiary, #1c2230);
  border: 0.0625rem solid var(--mb-border, rgba(255, 255, 255, 0.12));
  border-radius: var(--mb-radius-sm, 0.5rem);
}

.mb-buy__title {
  margin-bottom: var(--mb-space-sm, 1rem);
  font-size: var(--mb-font-size-xl, 1.25rem);
  font-weight: 600;
  color: var(--mb-text-primary, #fff);
}

/* ---------- Form layout: options | summary ---------- */

/* The form is EDD's own element, so the two-column split is applied to it
   directly rather than to a wrapper we'd have to inject around it.

   Explicit placement rather than auto-flow: EDD emits hidden inputs and, after
   an add, a checkout link as further children of this form. Hidden inputs
   aren't grid items (UA stylesheet gives them `display: none`), but pinning
   the three visible regions means anything else EDD adds later lands in the
   summary column beneath the button instead of displacing the grid. */
.mb-buy .edd_download_purchase_form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(9rem, 12rem);
  column-gap: var(--mb-space-md, 1.5rem);
  row-gap: var(--mb-space-sm, 1rem);
  align-items: start;
}

.mb-buy .mb-license {
  grid-column: 1;
  grid-row: 1 / span 2;
}

.mb-buy__summary {
  grid-column: 2;
  grid-row: 1;
}

.mb-buy .edd_purchase_submit_wrapper {
  grid-column: 2;
  grid-row: 2;
  /* The options column is taller, so without this the button would stretch to
     match it and float away from the total it belongs to. */
  align-self: start;
}

/* ---------- Total ---------- */

.mb-buy__total-label {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: var(--mb-letter-spacing-meta, 0.03em);
  text-transform: uppercase;
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
}

.mb-buy__total {
  margin: 0.125rem 0 0;
  font-size: var(--mb-font-size-xl, 1.25rem);
  font-weight: 700;
  /* The figure changes in place as options are chosen; lining figures stop the
     digits shuffling sideways on every change. */
  font-variant-numeric: tabular-nums;
  color: var(--mb-text-primary, #fff);
}

/* ---------- Licence cards ---------- */

/* A grid, not a stack. `auto-fit` + `minmax` means the column count follows
   the space available rather than a breakpoint guess, so two tiers sit side by
   side and five reflow to a second row without any extra rules. */
.mb-license__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.625rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.mb-license__item {
  position: relative; /* contains the absolutely-positioned input */
}

/* The radio is hidden but NOT removed: `display: none` would take it out of
   the tab order, leaving the choice unreachable by keyboard. Opacity keeps it
   focusable, and the ring is drawn on the card via :focus-visible below. */
.mb-license__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

/* Name above price rather than on one line: in a grid cell the two would
   otherwise be pushed to opposite edges of a narrow card with a gulf between
   them, and a long tier name would collide with its own price. */
.mb-license__card {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  height: 100%; /* equal-height cards across a row */
  padding: 0.875rem 1rem;
  background: var(--mb-surface-subtle, rgba(255, 255, 255, 0.05));
  border: 0.0625rem solid var(--mb-border, rgba(255, 255, 255, 0.12));
  /* Tighter than the panel containing it, for the same reason as the cover. */
  border-radius: 0.375rem;
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.mb-license__card:hover {
  border-color: var(--mb-border-strong, rgba(255, 255, 255, 0.2));
}

/* Selection is marked by border AND background, not colour alone — colour on
   its own fails for anyone who can't distinguish it. */
.mb-license__input:checked + .mb-license__card {
  background: var(--mb-surface-default, rgba(255, 255, 255, 0.08));
  border-color: var(--mb-accent, #3b82f6);
}

.mb-license__input:focus-visible + .mb-license__card {
  outline: 0.125rem solid var(--mb-text-primary, #fff);
  outline-offset: 0.125rem;
}

.mb-license__name {
  font-size: var(--mb-font-size-sm, 0.875rem);
  font-weight: 600;
  line-height: 1.3;
  color: var(--mb-text-primary, #fff);
}

.mb-license__price {
  font-size: var(--mb-font-size-sm, 0.875rem);
  font-variant-numeric: tabular-nums;
  color: var(--mb-text-secondary, rgba(255, 255, 255, 0.68));
  /* Pushed to the bottom of the card so prices line up across a row even when
     one tier's name wraps to two lines. */
  margin-top: auto;
}

/* ---------- EDD button ---------- */

/* Scoped to .mb-buy so EDD's buttons elsewhere (widgets, checkout) keep their
   own styling. */
.mb-buy .edd-add-to-cart,
.mb-buy .edd_go_to_checkout,
.mb-buy .edd-no-js {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: var(--mb-button-padding, 0.6875rem 1.375rem);
  background: var(--mb-button-bg, #415675);
  border: var(--mb-button-border-width, 0.0625rem) solid var(--mb-button-border, transparent);
  border-radius: var(--mb-radius-sm, 0.5rem);
  color: var(--mb-button-text, #fff);
  font-family: inherit;
  font-size: var(--mb-font-size-sm, 0.875rem);
  font-weight: 600;
  text-align: center;
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

.mb-buy .edd-add-to-cart:hover,
.mb-buy .edd_go_to_checkout:hover {
  background: var(--mb-button-bg-hover, transparent);
  border-color: var(--mb-button-border-hover, #fff);
}

/* The checkout link only appears after a successful add, and EDD toggles it
   inline — so it needs its own spacing without assuming it's always present. */
.mb-buy .edd_go_to_checkout {
  margin-top: 0.5rem;
}

.mb-buy .edd-cart-added-alert {
  display: block;
  margin-top: 0.5rem;
  font-size: var(--mb-font-size-sm, 0.875rem);
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
}

@media (max-width: 60rem) {
  .mb-single {
    /* Stack, artwork first: the cover is what confirms you're on the right
       item, and the licence cards mean nothing until you know that. */
    grid-template-columns: minmax(0, 1fr);
    gap: var(--mb-space-lg, 2.5rem);
  }

  /* Sticky in a single-column stack would pin the panel over the content
     below it, so it reverts to flowing normally. */
  .mb-single__buy {
    position: static;
  }

  .mb-cover {
    /* A full-width square eats an entire phone screen before any price is
       visible. Capped and left-aligned so the fold still reaches the facts. */
    max-width: 20rem;
  }
}

@media (max-width: 48rem) {
  /* One column: a 9rem summary track beside the cards leaves neither enough
     room. Total and button move below the grid, and the two sit on one line
     so the fold isn't spent on a mostly-empty row. */
  .mb-buy .edd_download_purchase_form {
    grid-template-columns: minmax(0, 1fr);
  }

  .mb-buy .mb-license {
    grid-column: 1;
    grid-row: auto;
  }

  .mb-buy__summary {
    grid-column: 1;
    grid-row: auto;
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
  }

  .mb-buy__total {
    margin-top: 0;
  }

  .mb-buy .edd_purchase_submit_wrapper {
    grid-column: 1;
    grid-row: auto;
  }
}

@media (max-width: 40rem) {
  /* The item title is the <h1> immediately below, so the final crumb is pure
     repetition at a width where the trail would otherwise wrap. Dropping it
     leaves the two crumbs that actually navigate. */
  .mb-crumbs__item:last-child {
    display: none;
  }
}


/* =========================================================
   Store — search / filter / sort bar

   Design rule for this component: no boxes that aren't doing work. The search
   field is bordered because it is a text input and needs to read as one; the
   dropdown toggles, the sort control and the chips are not. They are text and
   fills only, so the bar reads as a line of controls rather than a toolbar of
   competing rectangles.
   ========================================================= */

.mb-store-filters {
  /* The gap to the list is the whole reason this sits outside .mb-item-list.
     A filter bar tucked against its results reads as the first row. */
  margin-bottom: var(--mb-space-md, 1.5rem);
}

.mb-store-filters__bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--mb-space-sm, 1rem);
}

/* ---------- Search ---------- */

/* The wrapper owns the border and background; the <input> is transparent and
   chrome-free. Putting the frame on a parent is what lets the icon sit inside
   the same box as the text without absolute positioning. */
.mb-store-filters__search {
  display: flex;
  align-items: center;
  gap: var(--mb-space-2xs, 0.5rem);

  /* Grows to fill, but never past a comfortable line length — a search field
     spanning 80rem looks like an error, not an affordance. */
  flex: 1 1 14rem;
  max-width: 22rem;
  min-width: 0;

  height: 2.5rem;
  padding: 0 0.75rem;
  border: 0.0625rem solid var(--mb-border, rgba(255, 255, 255, 0.12));
  border-radius: var(--mb-radius-sm, 0.5rem);
  background: rgba(255, 255, 255, 0.04);
  transition: border-color var(--mb-duration-fast, 0.2s) ease,
              background-color var(--mb-duration-fast, 0.2s) ease;
}

.mb-store-filters__search:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: var(--mb-border-strong, rgba(255, 255, 255, 0.2));
}

/* `:focus-within` puts the focus ring on the visible box rather than on the
   transparent input hiding inside it. */
.mb-store-filters__search:focus-within {
  border-color: var(--mb-border-strong, rgba(255, 255, 255, 0.2));
  background: rgba(255, 255, 255, 0.06);
}

.mb-store-filters__search-icon {
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
}

.mb-store-filters__input {
  flex: 1;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--mb-text-primary, #fff);
  font-family: inherit;

  /* 16px on touch. Anything smaller and iOS zooms the viewport on focus,
     which leaves the page scrolled sideways after the keyboard closes. */
  font-size: 1rem;
  line-height: 1.5;
}

@media (min-width: 48rem) {
  .mb-store-filters__input {
    font-size: var(--mb-font-size-sm, 0.875rem);
  }
}

.mb-store-filters__input:focus {
  outline: 0;
}

.mb-store-filters__input::placeholder {
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
  opacity: 1;
}

/* WebKit's default clear button is a dark blob that's invisible on a dark
   field. Removed rather than restyled — it can't be restyled. */
.mb-store-filters__input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

/* ---------- Genre / Tags toggles ---------- */

.mb-store-filters__toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--mb-text-secondary, rgba(255, 255, 255, 0.68));
  font-family: inherit;
  font-size: var(--mb-font-size-sm, 0.875rem);
  cursor: pointer;
  transition: color var(--mb-duration-fast, 0.2s) ease;
}

.mb-store-filters__toggle:hover,
.mb-store-filters__toggle[aria-expanded='true'] {
  color: var(--mb-text-primary, #fff);
}

/* Focus ring is drawn on the text, not on a padded hit area — there is no box
   here to outline, and a floating rectangle around a bare word looks like a
   rendering fault. */
.mb-store-filters__toggle:focus-visible,
.mb-store-filters__select:focus-visible,
.mb-store-filters__clear:focus-visible,
.mb-store-filters__arrow:focus-visible,
.mb-store-filters__collapse:focus-visible,
.mb-chip:focus-visible {
  outline: 0.125rem solid var(--mb-text-primary, #fff);
  outline-offset: 0.25rem;
  border-radius: 0.125rem;
}

.mb-store-filters__chevron {
  width: 0.875rem;
  height: 0.875rem;

  /* Deliberately NOT rotated on open. The panel appearing below is already an
     unmistakable state change; spinning the caret as well is two animations
     reporting the same fact. */
  opacity: 0.7;
}

/* The count badge is a number, not a pill — at this size a filled circle
   round a single digit is heavier than the control it annotates. */
.mb-store-filters__count {
  min-width: 1.125rem;
  padding: 0 0.3125rem;
  border-radius: 62.5rem;
  background: var(--mb-surface-default, rgba(255, 255, 255, 0.08));
  color: var(--mb-text-primary, #fff);
  font-size: 0.6875rem;
  line-height: 1.125rem;
  text-align: center;
}

/* ---------- Sort ---------- */

/* A native <select> with its arrow suppressed and our own chevron overlaid.
   Native is kept on purpose: it inherits the platform's keyboard handling,
   type-ahead and mobile picker, none of which a custom listbox gets for free. */
/* Sort and layout live at the far end: they answer different questions from
   filtering, so they shouldn't sit in the same visual run as the facet
   toggles.

   The auto-margin is on this group rather than on each control. Two
   auto-margins in one flex row split the free space between them, which would
   have opened a gap down the middle of the pair. */
.mb-store-filters__end {
  display: flex;
  align-items: center;
  gap: var(--mb-space-sm, 1rem);
  margin-left: auto;
}

.mb-store-filters__sort {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.mb-store-filters__select {
  appearance: none;
  -webkit-appearance: none;
  padding: 0 1.125rem 0 0;
  border: 0;
  background: transparent;
  color: var(--mb-text-secondary, rgba(255, 255, 255, 0.68));
  font-family: inherit;
  font-size: var(--mb-font-size-sm, 0.875rem);
  cursor: pointer;
  transition: color var(--mb-duration-fast, 0.2s) ease;
}

.mb-store-filters__select:hover {
  color: var(--mb-text-primary, #fff);
}

/* The dropdown list itself is drawn by the OS, which does not inherit the
   page's dark palette — without this the options render black-on-white. */
.mb-store-filters__select option {
  background: var(--mb-bg-elevated, #202538);
  color: var(--mb-text-primary, #fff);
}

.mb-store-filters__sort .mb-store-filters__chevron {
  position: absolute;
  right: 0;
  pointer-events: none;
}

/* ---------- Clear ---------- */

.mb-store-filters__clear {
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
  font-family: inherit;
  font-size: var(--mb-font-size-sm, 0.875rem);
  text-decoration: underline;
  text-underline-offset: 0.1875rem;
  cursor: pointer;
  transition: color var(--mb-duration-fast, 0.2s) ease;
}

.mb-store-filters__clear:hover {
  color: var(--mb-text-primary, #fff);
}

/* ---------- Expanded panel ---------- */

/* Hairlines above and below rather than a bordered card: the panel is an
   extension of the bar, not a separate object floating over it. Two lines
   also cost less visual weight than four sides plus a radius. */
.mb-store-filters__panel {
  margin-top: var(--mb-space-sm, 1rem);
  padding: var(--mb-space-2xs, 0.5rem) 0;
  border-top: 0.0625rem solid var(--mb-border, rgba(255, 255, 255, 0.12));
  border-bottom: 0.0625rem solid var(--mb-border, rgba(255, 255, 255, 0.12));
}

.mb-store-filters__panel[hidden] {
  display: none;
}

.mb-store-filters__panel-head {
  display: flex;
  align-items: center;
  gap: var(--mb-space-2xs, 0.5rem);
  margin-bottom: var(--mb-space-2xs, 0.5rem);
}

/* Fill only, no border — it sits inside a region already delimited by the
   panel's hairlines, so a second frame would be a box inside a box. */
.mb-store-filters__chip-search {
  flex: 1;
  min-width: 0;
  padding: 0.375rem 0.625rem;
  border: 0;
  border-radius: var(--mb-radius-sm, 0.5rem);
  background: rgba(255, 255, 255, 0.04);
  color: var(--mb-text-primary, #fff);
  font-family: inherit;
  font-size: var(--mb-font-size-sm, 0.875rem);
}

.mb-store-filters__chip-search::placeholder {
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
  opacity: 1;
}

.mb-store-filters__chip-search:focus {
  outline: 0;
  background: rgba(255, 255, 255, 0.07);
}

.mb-store-filters__collapse,
.mb-store-filters__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
  cursor: pointer;
  transition: color var(--mb-duration-fast, 0.2s) ease;
}

.mb-store-filters__collapse:hover,
.mb-store-filters__arrow:hover {
  color: var(--mb-text-primary, #fff);
}

.mb-store-filters__collapse svg,
.mb-store-filters__arrow svg {
  width: 0.875rem;
  height: 0.875rem;
}

.mb-store-filters__arrow[hidden] {
  display: none;
}

/* ---------- Chip carousel ---------- */

.mb-store-filters__carousel {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

/* Scrolls instead of wrapping so opening a panel shifts the list down by a
   fixed, predictable amount rather than by however many rows of chips the
   catalogue happens to produce. */
.mb-store-filters__chips {
  display: flex;
  gap: 0.375rem;
  flex: 1;
  min-width: 0;
  padding: 0.125rem 0;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  scroll-behavior: smooth;
}

.mb-store-filters__chips::-webkit-scrollbar {
  display: none;
}

/* Selection is a step in white alpha (8% → 18%), not a colour change. An
   accent fill on a row of chips would out-shout the store list itself, which
   is the actual content. */
.mb-chip {
  flex: 0 0 auto;
  padding: 0.3125rem 0.6875rem;
  border: 0;
  border-radius: 62.5rem;
  background: var(--mb-surface-default, rgba(255, 255, 255, 0.08));
  color: var(--mb-text-secondary, rgba(255, 255, 255, 0.68));
  font-family: inherit;
  font-size: 0.75rem;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--mb-duration-fast, 0.2s) ease,
              color var(--mb-duration-fast, 0.2s) ease;
}

.mb-chip:hover {
  background: rgba(255, 255, 255, 0.12);
  color: var(--mb-text-primary, #fff);
}

.mb-chip[aria-pressed='true'] {
  background: rgba(255, 255, 255, 0.18);
  color: var(--mb-text-primary, #fff);
}

.mb-chip[aria-pressed='true']:hover {
  background: rgba(255, 255, 255, 0.22);
}

.mb-chip[hidden] {
  display: none;
}

/* ---------- Filtered-empty state ---------- */

/* Unbordered on purpose: this replaces the list, so repeating the list's frame
   would draw an empty box that looks like a broken row. */
.mb-store__empty {
  padding: var(--mb-space-md, 1.5rem) 0;
  color: var(--mb-text-secondary, rgba(255, 255, 255, 0.68));
  text-align: center;
}

/**
 * Hidden means hidden, in every layout.
 *
 * `[hidden]` is only `display: none` from the UA sheet, which any authored
 * `display` beats. The list rows carry `display: grid` in list view and
 * another in grid view, so each new layout is a fresh chance to silently
 * un-hide filtered rows — which is exactly what happened when grid view was
 * added: `[data-mb-view='grid'] .mb-item-list .mb-item` scores (0,3,0) against
 * this rule's (0,1,1), so filtering appeared to do nothing.
 *
 * `!important` rather than out-specifying it. Chasing specificity here means
 * every future layout variant has to remember to lose this race, and the
 * failure is invisible — the rows just stay on screen. `hidden` is an absolute
 * statement about whether an element participates at all, so it should not be
 * negotiable by layout rules. This is the case the keyword exists for.
 */
.mb-store__empty[hidden],
.mb-item-list[hidden],
.mb-item[hidden] {
  display: none !important;
}

/* ---------- Narrow screens ---------- */

@media (max-width: 47.9375rem) {
  /* The search takes the full first line and the facet controls share the
     second, rather than four controls fighting over one cramped row. */
  .mb-store-filters__search {
    flex-basis: 100%;
    max-width: none;
  }

  .mb-store-filters__bar {
    gap: var(--mb-space-xs, 0.75rem);
  }

  /* `margin-left: auto` would strand these controls alone on their own line
     once the bar wraps. */
  .mb-store-filters__end {
    margin-left: 0;
  }
}

/* ---------- Store list block ---------- */

/* The block reuses the grid section's header, so it needs the same gap beneath
   it that `.mb-grid-header + .mb-grid` provides there. Matched by value rather
   than by extending that selector, so a change to the grid can't silently
   reflow the store. */
.mb-store-block .mb-grid-header + .mb-store {
  margin-top: var(--mb-space-lg, 2.5rem);
}

/* Optional lead-in copy and CTA below the list.

   Centred and compact so it reads as a section close, not a second header.
   The text is intentionally constrained to a readable line length even when
   the list itself spans the full container width. */
.mb-store-block__footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--mb-space-sm, 1rem);
  margin-top: var(--mb-space-lg, 2.5rem);
  text-align: center;
}

.mb-store-block__lead {
  max-width: 44rem;
  margin: 0;
  font-size: var(--mb-font-size-base, 1rem);
  line-height: var(--mb-line-height-base, 1.6);
  color: var(--mb-text-secondary, rgba(255, 255, 255, 0.68));
}

/* Reuses the shared button primitive; only the icon motion is local so this
   CTA has a subtle directional cue without changing global button behaviour. */
.mb-store-block__cta [data-lucide],
.mb-store-block__cta svg {
  transition: transform 0.2s ease;
}

.mb-store-block__cta:hover [data-lucide],
.mb-store-block__cta:focus-visible [data-lucide],
.mb-store-block__cta:hover svg,
.mb-store-block__cta:focus-visible svg {
  transform: translateX(0.125rem);
}

@media (prefers-reduced-motion: reduce) {
  .mb-store-block__cta [data-lucide],
  .mb-store-block__cta svg {
    transition: none;
  }

  .mb-store-block__cta:hover [data-lucide],
  .mb-store-block__cta:focus-visible [data-lucide],
  .mb-store-block__cta:hover svg,
  .mb-store-block__cta:focus-visible svg {
    transform: none;
  }
}

/* =========================================================
   Store — layout toggle + grid view

   List and grid are ONE set of markup. Everything below keys off
   `[data-mb-view]` on the container, so switching layouts restyles the rows
   in place rather than rebuilding them — which is what keeps a playing
   preview playing and a decoded waveform decoded.

   ON SPECIFICITY: rules that override striping, hover or the waveform's
   mobile hide are written as `[data-mb-view='grid'] .mb-item-list .mb-item…`
   to reach (0,3,0). Their targets are (0,2,0) pairs that already depend on
   source order between themselves, and this file has twice been re-split. An
   override that only wins because it sits lower in the file is an override
   waiting to break.
   ========================================================= */

/* ---------- The toggle ---------- */

/* A track-and-thumb segmented control, which is a box that earns itself: it
   groups two mutually exclusive options and shows which is live. That is a
   different job from the bare text controls beside it, so reading differently
   from them is correct rather than inconsistent. */
.mb-view-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  padding: 0.125rem;
  border-radius: var(--mb-radius-sm, 0.5rem);
  background: rgba(255, 255, 255, 0.05);
}

/* 2.25rem (36px) rather than the 2.75rem used for the header nav toggle.
   36px clears the WCAG 2.2 AA minimum of 24px with room to spare; 44px is the
   AAA figure, and two of them would make this the tallest thing in a bar of
   text controls. */
.mb-view-toggle__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 0;
  /* Inset by the track's own padding so the thumb's curve sits concentric
     with the track's rather than parallel to it. */
  border-radius: calc(var(--mb-radius-sm, 0.5rem) - 0.125rem);
  background: transparent;
  color: var(--mb-text-tertiary, rgba(255, 255, 255, 0.48));
  cursor: pointer;
  transition: color 0.2s ease, background-color 0.2s ease;
}

.mb-view-toggle__button:hover {
  color: var(--mb-text-secondary, rgba(255, 255, 255, 0.68));
}

/* The active state is carried by `aria-pressed`, not a class, so the styling
   hook and the accessibility contract are the same attribute and cannot
   drift apart. */
.mb-view-toggle__button[aria-pressed='true'] {
  background: var(--mb-bg-elevated, #202538);
  color: var(--mb-text-primary, #fff);
}

.mb-view-toggle__button:focus-visible {
  outline: 0.125rem solid var(--mb-accent, #3b82f6);
  outline-offset: 0.125rem;
}

.mb-view-toggle__button svg {
  width: 1.125rem;
  height: 1.125rem;
}

/* The standalone rail, used when there is no filter bar for the toggle to
   sit in. */
.mb-store__bar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--mb-space-md, 1.5rem);
}

/* ---------- Grid view ---------- */

/* The container frame belongs to the list: in grid view each card carries its
   own edge, so a border around the whole set would draw a box around boxes. */
[data-mb-view='grid'] .mb-item-list {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--mb-space-md, 1.5rem);
  overflow: visible;
  border: 0;
  border-radius: 0;
}

/**
 * The card.
 *
 * Grid rather than flex, because the bottom row holds two siblings that have
 * to share it — the waveform and the CTA are not wrapped in a common parent,
 * and adding one purely for grid view would put layout scaffolding into the
 * markup that list view has no use for.
 *
 * Rows: artwork, meta, then `1fr` for the bottom row. The `1fr` is what makes
 * a column of cards line their CTAs up: cards in a grid row already stretch to
 * the tallest, and without a flexible row that extra height would be
 * distributed across all three instead of collecting at the bottom.
 */
[data-mb-view='grid'] .mb-item-list .mb-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto 1fr;
  gap: 0.75rem;
  padding: 0.875rem;
  overflow: hidden;
  border: 0.0625rem solid var(--mb-border, rgba(255, 255, 255, 0.12));
  border-radius: var(--mb-radius-sm, 0.5rem);
  /* Flat, not striped. Alternating shades order a single column; in a grid
     they'd read as an arbitrary checkerboard. */
  background: var(--mb-bg-tertiary, #1c2230);
}

/* Hover lifts the surface, matching the list. The border brightens too
   because a card has an edge to brighten and a row does not. */
[data-mb-view='grid'] .mb-item-list .mb-item:hover {
  background: var(--mb-bg-elevated, #202538);
  border-color: var(--mb-border-strong, rgba(255, 255, 255, 0.2));
}

[data-mb-view='grid'] .mb-item-list .mb-item:focus-visible {
  border-color: var(--mb-border-strong, rgba(255, 255, 255, 0.2));
  box-shadow: none;
  background: var(--mb-bg-elevated, #202538);
}

/* Artwork is full-bleed: it escapes the card's padding with a negative margin
   rather than the card dropping its padding and every other child gaining
   its own. One value to change instead of three to keep in sync. */
[data-mb-view='grid'] .mb-item__art {
  grid-column: 1 / -1;
  width: auto;
  height: auto;
  aspect-ratio: 1;
  margin: -0.875rem -0.875rem 0;
  border-radius: 0;
}

/* The scrim now covers a card-sized area, so a 20px glyph in the middle of it
   reads as a blemish rather than a button. */
[data-mb-view='grid'] .mb-item__icon,
[data-mb-view='grid'] .mb-item__play svg {
  width: 2.25rem;
  height: 2.25rem;
}

[data-mb-view='grid'] .mb-item__meta {
  grid-column: 1 / -1;
}

/* Two lines, then clamp. Wrapping is right here — a card is narrow enough
   that a single truncated line would cut into the first genre — but unbounded
   wrapping would let one heavily-tagged item set the height of its whole row. */
[data-mb-view='grid'] .mb-item__facets {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  white-space: normal;
}

/* Bottom row: waveform takes the slack, CTA sizes to its label. */
[data-mb-view='grid'] .mb-item__wave {
  grid-column: 1;
  align-self: center;
  height: 2rem;
}

[data-mb-view='grid'] .mb-item__actions {
  grid-column: 2;
  align-self: center;
}

/* The list view hides waveforms below 64rem — at that width a row cannot give
   one enough space to be scrubbable, and the decode isn't worth the bytes.
   A card at 2-up is wider than that row was, so the reasoning doesn't carry
   over and the waveform is restored. It stays hidden on phones: see the
   single-column rule further down. */
[data-mb-view='grid'] .mb-item-list .mb-item__wave {
  display: block;
}

[data-mb-view='grid'] .mb-item-list .mb-item__wave--empty {
  display: none;
}

/* Two columns from tablet down. */
@media (max-width: 64rem) {
  [data-mb-view='grid'] .mb-item-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* One column on phones.
 *
 * The waveform goes with it, restoring the list view's decision rather than
 * quietly reversing it: decoding multi-megabyte previews over mobile data is
 * the cost that rule existed to avoid, and a single-column card is no better
 * placed to justify it than a row was. */
@media (max-width: 40rem) {
  [data-mb-view='grid'] .mb-item-list {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--mb-space-sm, 1rem);
  }

  [data-mb-view='grid'] .mb-item-list .mb-item__wave {
    display: none;
  }

  /* With the waveform gone the bottom row has one occupant, so the CTA
     stretches to the full card width instead of floating at the end of an
     otherwise empty line. */
  [data-mb-view='grid'] .mb-item-list .mb-item__actions {
    grid-column: 1 / -1;
  }

  [data-mb-view='grid'] .mb-item-list .mb-item__cta {
    width: 100%;
  }
}
