/* === GENERATED: brand accent — do not edit by hand === */
/*
 * Violet. Cygnus is a studio, and violet is where audio software has lived for years — pushed more saturated than the library default so it reads as a choice rather than as an app nobody themed.
 *
 * Seed #7A3FB5, expanded by `@awc-ui/theme`'s computeTheme into the full
 * tonal set. Regenerate with `pnpm --filter @awc-ui/showcase-kit generate:brands`;
 * editing the declarations below by hand breaks the contrast pairings the
 * theme package guarantees.
 */
:root{--md-sys-color-primary:#7B40B6;--md-sys-color-on-primary:#FFFFFF;--md-sys-color-primary-container:#F0DBFF;--md-sys-color-on-primary-container:#62249C;--md-sys-color-inverse-primary:#DCB8FF;--md-sys-color-secondary:#665A6F;--md-sys-color-on-secondary:#FFFFFF;--md-sys-color-secondary-container:#EDDDF6;--md-sys-color-on-secondary-container:#4D4357;--md-sys-color-tertiary:#805158;--md-sys-color-on-tertiary:#FFFFFF;--md-sys-color-tertiary-container:#FFD9DD;--md-sys-color-on-tertiary-container:#653A41;--md-sys-color-error:#BA1A1A;--md-sys-color-on-error:#FFFFFF;--md-sys-color-error-container:#FFDAD6;--md-sys-color-on-error-container:#93000A;--md-sys-color-background:#FEF7FC;--md-sys-color-on-background:#1D1B1E;--md-sys-color-surface:#FEF7FC;--md-sys-color-on-surface:#1D1B1E;--md-sys-color-surface-variant:#E9DFEB;--md-sys-color-on-surface-variant:#4A454E;--md-sys-color-surface-container-lowest:#FFFFFF;--md-sys-color-surface-container-low:#F8F2F6;--md-sys-color-surface-container:#F3ECF1;--md-sys-color-surface-container-high:#EDE6EB;--md-sys-color-surface-container-highest:#E7E1E5;--md-sys-color-surface-dim:#DED8DD;--md-sys-color-surface-bright:#FEF7FC;--md-sys-color-outline:#7C757E;--md-sys-color-outline-variant:#CCC4CE;--md-sys-color-inverse-surface:#322F33;--md-sys-color-inverse-on-surface:#F5EFF4;--md-sys-color-shadow:#000000;--md-sys-color-scrim:#000000;--md-sys-color-surface-tint:#7B40B6}[data-theme="dark"]{--md-sys-color-primary:#DCB8FF;--md-sys-color-on-primary:#490081;--md-sys-color-primary-container:#62249C;--md-sys-color-on-primary-container:#F0DBFF;--md-sys-color-inverse-primary:#7B40B6;--md-sys-color-secondary:#D0C1DA;--md-sys-color-on-secondary:#362C3F;--md-sys-color-secondary-container:#4D4357;--md-sys-color-on-secondary-container:#EDDDF6;--md-sys-color-tertiary:#F3B7BE;--md-sys-color-on-tertiary:#4B252B;--md-sys-color-tertiary-container:#653A41;--md-sys-color-on-tertiary-container:#FFD9DD;--md-sys-color-error:#FFB4AB;--md-sys-color-on-error:#690005;--md-sys-color-error-container:#93000A;--md-sys-color-on-error-container:#FFDAD6;--md-sys-color-background:#151316;--md-sys-color-on-background:#E7E1E5;--md-sys-color-surface:#151316;--md-sys-color-on-surface:#E7E1E5;--md-sys-color-surface-variant:#4A454E;--md-sys-color-on-surface-variant:#CCC4CE;--md-sys-color-surface-container-lowest:#0F0D11;--md-sys-color-surface-container-low:#1D1B1E;--md-sys-color-surface-container:#211F22;--md-sys-color-surface-container-high:#2C292D;--md-sys-color-surface-container-highest:#373437;--md-sys-color-surface-dim:#151316;--md-sys-color-surface-bright:#3B383C;--md-sys-color-outline:#968E98;--md-sys-color-outline-variant:#4A454E;--md-sys-color-inverse-surface:#E7E1E5;--md-sys-color-inverse-on-surface:#322F33;--md-sys-color-shadow:#000000;--md-sys-color-scrim:#000000;--md-sys-color-surface-tint:#DCB8FF}
/* === END GENERATED brand accent === */

/**
 * The Cygnus app's own stylesheet — the shell, the layout primitives and the
 * named blocks this vertical draws.
 *
 * Shared by all five framework builds, which is the point: the shell that holds
 * a screen must be pixel-identical across ports or two screenshots of the same
 * screen stop being comparable evidence. Everything here is either structure
 * (the rail, the app bar, the screen body) or a primitive the screens compose
 * (`.panel`, `.row`, `.stack`), and every colour, radius and spacing value is
 * an M3 token — there is no literal hex in this file.
 *
 * WHAT IS NOT HERE: anything a component already does. If a card needs a
 * surface, it is an `md-card`; if a figure needs a colour, it comes from
 * `status.ts`. These rules lay things out and nothing more.
 *
 * THE SHELL AND LAYOUT SECTIONS ARE THE SAME SHAPE AS THE OTHER FOUR
 * VERTICALS' — that duplication is deliberate and matches the existing
 * convention: one stylesheet per vertical, so a change to Corvus cannot
 * silently reflow this app. What is genuinely different begins at "named
 * blocks", and the biggest of those differences is the TIMELINE, which exists
 * in the shape it does because of a content-security policy rather than a
 * design preference — see the long note above `.lane`.
 */

/*
 * `hidden` MEANS HIDDEN, whatever a class underneath it says.
 *
 * The user-agent's `[hidden] { display: none }` is a plain type-less rule, so
 * ANY class rule in this file outranks it: `.post-grid { display: grid }` and
 * `.feed__end { display: flex }` both win, and an element written with `hidden`
 * stays on the screen. The plain-HTML build leans on the attribute for every
 * one of its state swaps — the queue panel, the edit history, a lane's clips
 * when a project is swapped — so this is load-bearing there rather than
 * defensive. It was found by the parity check on an earlier vertical, where a
 * profile rendered three tab panels stacked on top of each other.
 *
 * `!important` because the alternative is remembering to out-specify it in
 * every rule that sets `display`, which is the same bug waiting on the next
 * block anyone adds.
 */
[hidden] {
  display: none !important;
}

/*
 * The application's mount node.
 *
 * `display: contents` so the framework's root element is transparent to layout
 * and `.shell` below behaves as though it were a child of <body> — every build
 * mounts into a div, and without this each one would introduce a block box the
 * others do not have, which the parity check reads as a layout difference.
 *
 * A CLASS, not the `style="display: contents"` attribute this used to be: an
 * enterprise Content-Security-Policy with `style-src-attr 'none'` refuses inline
 * style attributes, and that one was in the shipped HTML of every build.
 */
.awc-root {
  display: contents;
}

* {
  box-sizing: border-box;
}

html {
  /* The dock also writes lang/dir/data-theme/data-density here. */
  color-scheme: light dark;

  /*
   * THE ROUNDED CUT OF MATERIAL SYMBOLS, library-wide, from one line.
   *
   * `packages/tokens` defaults this to the Outlined cut and documents exactly
   * this swap. Outlined draws square terminals and sharp points — a FILLED star
   * comes out spiky — while Rounded softens them, which is the shape the chips,
   * meters and rating on these screens read best against, and the one Storybook
   * shows because it loads both faces. Every component reads the token, so this
   * is not a per-component decision and cannot drift into one.
   *
   * Outlined stays behind it in the stack: each build's `index.html` decides
   * which faces it actually loads, and one that has only Outlined falls back to
   * it rather than to a serif.
   */
  --md-sys-icon-font-family: 'Material Symbols Rounded', 'Material Symbols Outlined', 'Material Icons';
}

body {
  margin: 0;
  background: var(--md-sys-color-surface);
  color: var(--md-sys-color-on-surface);
  font: var(--md-sys-typescale-body-medium-font);
  /* <awc-showcase-dock> is position: fixed and publishes its measured height
     here. Without this the last row of every screen sits under the bar — and
     so does the navigation bar, which docks immediately above the dock. */
  padding-block-end: var(--awc-dock-height, 0px);
}

a {
  color: inherit;
}

/* ------------------------------------------------------------------- shell */

/*
 * The app frame: app bar across the top, then rail beside content.
 *
 * THE FRAME DOES NOT SCROLL — ONLY `.shell__main` DOES, and that is the whole
 * point of a fixed block-size here rather than a minimum. When the DOCUMENT
 * scrolled, two things went wrong at once: the content slid underneath the
 * sticky app bar, clipping the breadcrumb row against it, and the navigation
 * rail scrolled away with the page even though a destination list is supposed
 * to stay put. Both are the same bug — there was no scroll port other than the
 * viewport, so everything moved together.
 *
 * The height subtracts the dock, because <awc-showcase-dock> is fixed to the
 * bottom and `body` already reserves its measured height as padding. A frame of
 * a full `100dvh` inside that padded body would overflow by exactly the dock's
 * height and put the document scrollbar straight back.
 */
.shell {
  display: flex;
  flex-direction: column;
  block-size: calc(100dvh - var(--awc-dock-height, 0px));
  overflow: hidden;
  /* The rail's `modal` overlay and scrim position against the nearest
     positioned ancestor. This build does not use modal, but the rail's manual
     is explicit that a static ancestor is where that breaks, and the cost of
     being right in advance is one declaration. */
  position: relative;
}

/*
 * Rail and content, side by side.
 *
 * `min-inline-size: 0` on the main column is load-bearing: without it a grid
 * item refuses to shrink below the intrinsic width of its widest child, so one
 * wide table would push the whole layout past the viewport and produce a
 * horizontal scrollbar on the DOCUMENT instead of inside the table's own
 * scroll port.
 */
.shell__body {
  display: flex;
  flex: 1 1 auto;
  /* `min-block-size: 0` lets this row shrink inside the fixed frame; without
     it a flex item floors at its content height and pushes the overflow back
     out to the document. */
  min-block-size: 0;
  overflow: hidden;
}

/*
 * The rail is a destination list, so it stays where it is.
 *
 * It sits outside `.shell__main`'s scroll port, so it no longer moves with the
 * content. `overflow: hidden` is the guard for the other direction: a rail with
 * more destinations than fit would otherwise grow the row and hand the frame
 * its scrollbar back.
 */
.shell__rail {
  flex: 0 0 auto;
  overflow: hidden;
  /* The rail fills the row, and the row is what bounds it. It used to carry
     `full-height`, which sizes against the VIEWPORT — taller than this row now
     that the app bar sits above it in a fixed frame, so the rail overflowed
     `.shell__body` by exactly the bar's height and its last destination was
     clipped. The attribute is gone; this is the correct bound. */
  block-size: 100%;
  border-inline-end: 1px solid var(--md-sys-color-outline-variant);
  /* The rail's pre-upgrade width floor used to be hand-copied here; it now
     ships with the library (`@awc-ui/core/css/pre-upgrade.css`, imported in
     main.tsx), generated from the component's own :host at build time. */
}

/*
 * The FAB band needs no reservation any more. `md-navigation-rail` now seeds
 * `hasFab` from the light DOM in `componentWillLoad`, so the band is open from
 * the rail's very first painted frame — the repair the 40-line comment that
 * used to sit here asked for by name. The two `::part()` rules it justified
 * (and their coupling to the rail's internal `--with-fab` class) went with it.
 */

/*
 * The one scroll port in the app.
 *
 * `overscroll-behavior: contain` stops a scroll that reaches the end of this
 * column from chaining out to the document and bouncing the whole frame.
 */
.shell__main {
  flex: 1 1 auto;
  min-inline-size: 0;
  min-block-size: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: var(--md-sys-spacing-gap-lg, 16px);
  /* Full width on purpose. A statement is read ACROSS — the transaction
     table alone carries twelve columns — and capping the content at a reading
     measure only forces horizontal scrolling inside the tables while leaving
     the screen empty on both sides. */
  padding-inline: var(--md-sys-spacing-inset-xl, 24px);
  padding-block: var(--md-sys-spacing-inset-lg, 16px) var(--md-sys-spacing-inset-xl, 24px);
}

/*
 * Keep every row of the screen at its natural height.
 *
 * `.shell__main` is a flex column with a bounded height, and a flex item's
 * default `flex-shrink: 1` means the screen is COMPRESSED to fit rather than
 * overflowing — so the column never gains anything to scroll, and the content
 * is quietly squashed instead. Pinning the children to their content height is
 * what turns the bounded column into a real scroll port.
 */
.shell__main > * {
  flex-shrink: 0;
}

/*
 * The bottom bar sits ABOVE the dock, not under it.
 *
 * `--awc-dock-height` is published by <awc-showcase-dock> once it has measured
 * itself; the fallback of 0 is what a first frame (or a dismissed dock) gets,
 * and the bar simply sits on the viewport edge then.
 */
.shell__bar {
  position: fixed;
  inset-inline: 0;
  inset-block-end: var(--awc-dock-height, 0px);
  z-index: var(--md-sys-z-index-navigation, 200);
  /* This box is anchored by its BOTTOM edge, so a 0-height first frame put the
     bar's top at the very bottom of the viewport and the surface climbed into
     place. The height floor that prevented it used to be hand-copied here; the
     slotted `md-navigation-bar` now floors itself via the library's
     pre-upgrade sheet, and this wrapper takes its height from that. */
}

/*
 * ONE navigation surface at a time.
 *
 * M3 is explicit in both directions: a bar is for compact, a rail is for
 * medium and expanded, and shipping both at once means two active indicators
 * claiming to be the current destination. `display: none` rather than
 * `visibility` so the hidden one is out of the accessibility tree too — a
 * screen reader finding two "Main navigation" landmarks with the same five
 * destinations is the audible version of the same bug.
 *
 * 900px is where the twelve-column holdings table stops fitting beside an 80px
 * rail. Below it the rail's width is worth more to the table than the labels
 * are to the reader.
 */
.shell__bar {
  display: none;
}

@media (max-width: 899px) {
  .shell__rail {
    display: none;
  }

  .shell__bar {
    display: block;
  }

  .shell__main {
    padding-inline: var(--md-sys-spacing-inset-md, 12px);
    /* Clear the docked bar, which is out of flow. 80px is its own height at
       density 0; it tightens with the density rungs, and the extra few pixels
       at a compact rung are harmless whitespace under the last panel. */
    padding-block-end: calc(80px + var(--md-sys-spacing-inset-xl, 24px));
  }
}

/* --------------------------------------------------------------- app bar */

/*
 * The masthead is `role="banner"` and `position: sticky` on its own — the
 * component does both. What is set here is only what a HOST decides: the tone
 * that separates it from the content beneath, and the rule that replaces the
 * elevation M3 does not give a small bar.
 */
.shell__appbar {
  --md-app-bar-container-color: var(--md-sys-color-surface-container-low);
  border-block-end: 1px solid var(--md-sys-color-outline-variant);
  /* The library's pre-upgrade sheet floors `md-app-bar` at its own row height;
     what it cannot know about is the 1px border THIS app draws two lines up,
     which `border-box` counts inside the box. This rule exists for that pixel:
     without it the floor is one short of the settled 65px and the frame nudges
     1px on hydration. The base expression must match the component's — the
     sheet is generated against it, so drift shows up there first. */
  min-block-size: calc(
    var(--md-app-bar-row-height, max(48px, calc(64px + var(--md-sys-density-scale, 0) * 4px))) + 1px
  );
}

/*
 * The screen's body and the placeholder painted over it.
 *
 * THE STAGE IS THE POSITIONING CONTEXT, AND THAT IS ITS WHOLE JOB. The real
 * content keeps its box for the entire life of the screen; the placeholder is
 * taken out of flow and laid over the top. Revealing is a `visibility` flip, so
 * there is no reflow to get wrong — 0px of movement at every width, density and
 * locale, by construction rather than by arithmetic.
 *
 * The arithmetic is what this replaced, and it could not have worked: measured
 * on holdings, `.grid-3` is 268 tall at 1600 and 464 at 1024, the concentration
 * panel 498 at 1600 and 1384 at 720, and the KPI grid's second row is shorter
 * than its first because those two tiles hold less. A placeholder that occupies
 * layout has to predict all of that; hard-coded heights matched at the single
 * width they were measured at and drifted up to 1102px elsewhere.
 *
 * `.screen-body` still restates the column and the gap, because it sits between
 * `.shell__main` and the sections that used to be its direct children.
 */
.screen-stage {
  position: relative;
  min-inline-size: 0;
}

.screen-body {
  display: flex;
  flex-direction: column;
  gap: var(--md-sys-spacing-gap-lg, 16px);
  min-inline-size: 0;
}

/*
 * `visibility`, never `display: none`.
 *
 * `display: none` would take the box away and hand back the collapse this whole
 * arrangement exists to prevent. `visibility: hidden` keeps the layout and — the
 * part that matters for a screen reader — takes the content out of the
 * accessibility tree, so the placeholder is the only thing announcing.
 */
.screen-body[data-placeholder] {
  visibility: hidden;
}

/*
 * The members list's action cluster, hidden while nothing is ticked.
 *
 * Same reasoning as the placeholder above — `visibility` keeps the space the
 * cluster occupies, so the list below does not jump down the moment a row is
 * selected, and takes the button out of the tab order and the accessibility
 * tree while it is inert, which `opacity: 0` would not.
 *
 * DRIVEN BY `aria-hidden`, which the row already carries: one attribute keeps
 * what is seen and what is announced in step, and it is a class rather than a
 * `style="…"` attribute because the plain-HTML build ships this state in the
 * document and a strict `style-src` refuses inline styles. (React and Vue
 * write the same state through CSSOM, which is exempt.)
 */
[data-cluster][aria-hidden='true'] {
  visibility: hidden;
}

/* Same reason as `.shell__main > *`: in a bounded flex column, children
   compress to fit instead of overflowing unless they are told not to. */
.screen-body > * {
  flex-shrink: 0;
}

/*
 * Out of flow, over the content — and sized by ITSELF, not by the stage.
 *
 * `inset: 0` was the obvious way to write this and it is wrong. The stage takes
 * its height from the real content, and the real content spends its first
 * frames un-upgraded and short: measured on holdings, the stage was 148px for
 * three frames before the components hydrated and it became 1996. A placeholder
 * stretched to `inset: 0` is clipped to that 148px and then snaps open — the
 * same flicker as before, moved from the content onto the thing covering it.
 *
 * So only the top and the inline edges are pinned, and the block size is the
 * placeholder's own. It paints at full height from its first frame no matter
 * what the stage is doing underneath, and because it is out of flow it still
 * cannot move anything.
 */
.screen-stage__placeholder {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  display: flex;
  flex-direction: column;
  /*
   * TIGHTER THAN THE CONTENT IT COVERS, ON PURPOSE.
   *
   * The real body uses the 16px section gap because those sections are separate
   * pieces of furniture a reader has to tell apart. A placeholder has nothing to
   * tell apart — it is one object suggesting a shape — and at 16px its bars read
   * as scattered slabs with holes between them. This is only affordable because
   * the placeholder no longer occupies layout: its spacing is appearance now,
   * not structure, so it can be set by how it looks rather than by what it has
   * to match.
   */
  gap: var(--md-sys-spacing-gap-xs, 4px);
  min-inline-size: 0;
}

/* Same reasoning one level down: rows inside a placeholder sit tight to each
   other rather than at the content's own rhythm. */
.screen-stage__placeholder .stack {
  gap: var(--md-sys-spacing-gap-xs, 4px);
}

.screen-stage__placeholder > * {
  flex-shrink: 0;
}

/*
 * ─────────────────────────────────────────────────────────────────────────
 * PLACEHOLDER PRIMITIVES — plain DOM, and that is the entire point.
 *
 * The placeholder used to be built out of `md-skeleton` and `md-card`. Those
 * are lazily-hydrated custom elements exactly like the content they stand in
 * for, so the placeholder had the same disease as the thing it was treating:
 * measured on holdings, it was 172px for three frames, 228px for one, and only
 * then 1986px. A loading state that pops open cannot hide a loading state that
 * pops open.
 *
 * Divs have their size from the first paint, before a single chunk has loaded.
 * They cost nothing, they cannot fail to upgrade, and a placeholder needs none
 * of what a component gives you — no slots, no a11y semantics of its own, no
 * variants. It is a grey rectangle with a shimmer.
 *
 * Everything below is tokens only, so the placeholder themes, densifies and
 * mirrors with the rest of the sheet.
 * ─────────────────────────────────────────────────────────────────────────
 */
.skel {
  display: block;
  background: var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-extra-small, 4px);
  flex-shrink: 0;
  /* The shimmer travels across a gradient wider than the box. `background-size`
     and the keyframes are the only pair that has to agree here. */
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--md-sys-color-surface-bright) 55%, transparent) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: skel-shimmer 1.4s var(--md-sys-motion-easing-standard, cubic-bezier(0.2, 0, 0, 1)) infinite;
}

@keyframes skel-shimmer {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

/* A skeleton is decoration. Honour the reader's setting: hold the surface, drop
   the movement. */
@media (prefers-reduced-motion: reduce) {
  .skel {
    animation: none;
  }
}

.skel--pill {
  border-radius: var(--md-sys-shape-corner-full, 9999px);
}

.skel--circle {
  border-radius: 50%;
}

/*
 * A card-shaped placeholder surface: the outlined card's own tokens, without
 * the component. `md-card` publishes these, so the two cannot drift apart
 * without the tokens changing for both.
 */
.skel-card {
  display: flex;
  flex-direction: column;
  gap: var(--md-sys-spacing-gap-xs, 4px);
  padding: max(8px, calc(16px + var(--md-sys-density-scale, 0) * 2px));
  border-radius: var(--md-sys-shape-corner-medium, 12px);
  background: var(--md-sys-color-surface-container-low);
  box-sizing: border-box;
}

.skel-card--filled {
  background: var(--md-sys-color-surface-container-highest);
}

/*
 * The brand and the demo chip, sharing the app bar's title line.
 *
 * `inline-flex` so the pair sits on the baseline row the bar already lays out
 * — the span is slotted into `part="title"`, so a block here would take the
 * chip out of the title's own line box. The chip brings its own typescale, so
 * it does not inherit the headline's size.
 */
.shell__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--md-sys-spacing-gap-sm, 8px);
  min-inline-size: 0;
}

/* The reporting context, in the app bar's trailing cluster. */
.shell__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--md-sys-spacing-gap-md, 12px);
  font: var(--md-sys-typescale-label-medium-font);
  color: var(--md-sys-color-on-surface-variant);
  padding-inline-end: var(--md-sys-spacing-inset-sm, 8px);
}

/* Below the rail breakpoint the reporting context is the first thing to go —
   the app bar has three trailing children at most, and on a phone the date
   matters less than the actions do. */
@media (max-width: 719px) {
  .shell__meta {
    display: none;
  }
}

/* ------------------------------------------------------------ breadcrumbs */

/*
 * The trail row, whose HEIGHT is reserved whether or not it has a trail in it.
 *
 * The trail only appears on the drill into a household; the five destination
 * screens get their "where am I" from the rail's active indicator, so a trail
 * there would only repeat it. But an empty row still has to occupy the same
 * space as a full one, because a row that comes and goes moves the heading and
 * every panel under it on each navigation — which is exactly what "jumpy"
 * means. `min-block-size` rather than a fixed height, so a trail that wraps on
 * a narrow viewport can still grow.
 */
.shell__trail {
  display: flex;
  align-items: center;
  min-block-size: 36px;
}

/**
 * DON'T RESERVE THE TRAIL BAND ON A PHONE.
 *
 * The reservation exists so the heading does not jump between a screen with
 * breadcrumbs and one without. That is worth 36px on a desktop and it is not
 * worth 36px of an 844px phone — especially here, where only the two drill
 * screens have a trail at all and you reach one by tapping a row, so the whole
 * page changes anyway and there is no jump left to prevent.
 *
 * The band still takes its natural height where it has crumbs; only the empty
 * reservation goes.
 */
@media (max-width: 719px) {
  .shell__trail {
    min-block-size: 0;
  }
}

/* ------------------------------------------------------------- screen head */

.screen-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--md-sys-spacing-gap-md, 12px);
}

.screen-head__text {
  min-inline-size: 0;
  flex: 1 1 260px;
}

.screen-head h1 {
  font: var(--md-sys-typescale-headline-medium-font);
  margin: 0;
}

.screen-head p {
  font: var(--md-sys-typescale-body-medium-font);
  color: var(--md-sys-color-on-surface-variant);
  margin: var(--md-sys-spacing-gap-xs, 4px) 0 0;
}

.screen-head__aside {
  display: flex;
  align-items: center;
  gap: var(--md-sys-spacing-gap-sm, 8px);
  flex-wrap: wrap;
}

/*
 * The screen's own action strip.
 *
 * `md-toolbar variant="floating"` is an inline pill, which is why it can live
 * here at all — the docked variant is `position: sticky` against the BOTTOM
 * edge, where the navigation bar and the dock already are. Aligned to the end
 * so the actions sit opposite the heading rather than under it.
 */
.screen-toolbar {
  display: flex;
  justify-content: flex-end;
  /* The 64px height floor that stopped this row collapsing pre-upgrade used to
     be hand-copied here; the floating `md-toolbar` inside it now floors itself
     via the library's pre-upgrade sheet, and this row takes its height from
     that. */
}

/*
 * NO OUTER MARGIN ON THE PILL.
 *
 * `md-toolbar` gives its floating variant `margin-inline: 16px` — right for a
 * toolbar floating free over a canvas, where that margin is what keeps it off
 * the viewport edge. This one is not floating over anything: it sits in a
 * screen row that `.shell__main` has already inset by 24px, so the margin was
 * a second inset stacked on the first and the pill hung 16px short of the
 * trailing edge every other block on the screen lines up to.
 *
 * Zeroed here and not in the component: the component's default is correct for
 * its own case, and this app is the one placing it in an already-inset row.
 */
.screen-toolbar md-toolbar {
  margin-inline: 0;
}

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

.panel {
  display: block;
}

/*
 * A PLACEHOLDER'S PANEL, matching an outlined `md-card`'s box.
 *
 * The skeletons cannot use `md-card`: it is a lazily-hydrated custom element
 * exactly like the content it stands in for, so a placeholder built from one
 * catches the disease it exists to treat — see the header of `skeletons.tsx`.
 * But a bare div has no border, no radius and no surface, so the placeholder
 * was a column of loose grey bars floating on the page while the real screen
 * showed four bordered cards. It read as a different layout, which is the one
 * thing a placeholder must never do.
 *
 * It shares `.panel__inner` with the real panel rather than repeating the
 * padding, so the two cannot drift.
 */
.skel-panel {
  display: block;
  border-radius: var(--md-sys-shape-corner-medium);
  background: var(--md-sys-color-surface);
  /*
   * `md-card` PADS ITS OWN HOST BY 16px, on top of `.panel__inner`'s 16. Both
   * are needed or the placeholder is exactly 32px shorter than the panel it
   * covers — per panel, which compounds down a four-panel screen into a reveal
   * that visibly jumps.
   */
  padding: var(--md-sys-spacing-inset-lg, 16px);
  /*
   * AN OUTLINE, NOT A BORDER, and that is the whole trick. A 1px border adds
   * 2px to the box and would leave the placeholder 2px taller than the card it
   * stands in for — the card's own edge costs nothing because it is inside the
   * component's box. An outline draws in the same place and takes no space, so
   * the two boxes measure identically.
   */
  outline: 1px solid var(--md-sys-color-outline-variant);
  outline-offset: -1px;
}

.panel__inner {
  padding: var(--md-sys-spacing-inset-lg, 16px);
  display: flex;
  flex-direction: column;
  gap: var(--md-sys-spacing-gap-md, 12px);
  min-inline-size: 0;
}

.panel__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--md-sys-spacing-gap-sm, 8px);
  flex-wrap: wrap;
}

/*
 * Title and subtitle are ONE heading: they share a line, and the subtitle
 * drops beneath the title only when the card is too narrow to hold both. That
 * is `flex-wrap`, not a media query — the width that runs out is the CARD's,
 * and the same panel is full-bleed on one screen and a third of a row on
 * another, so no viewport breakpoint can name the moment it happens.
 *
 * `row-gap: 0` because the wrapped form has to stay exactly the stacked form
 * it replaces: title line box, subtitle line box, nothing between them. Only
 * the column gap separates the two while they share a line.
 *
 * `min-inline-size: 0` defeats the automatic minimum size a flex item takes
 * from its content, which would otherwise push this heading — and with it the
 * head — wider than the card instead of wrapping inside it. It does not make
 * the heading collapse early: `.panel__head` decides whether `actions` share
 * this row from CONTENT widths, before any item is shrunk. So the order of
 * retreat is subtitle beside title, then actions to their own row, then
 * subtitle under title, and only then the character break below.
 */
.panel__heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--md-sys-spacing-gap-sm, 8px);
  row-gap: 0;
  min-inline-size: 0;
}

.panel__title {
  font: var(--md-sys-typescale-title-small-font);
  margin: 0;
  min-inline-size: 0;
  /*
   * `anywhere`, not `break-word`: only `anywhere` also shrinks the intrinsic
   * min-content width, which is what keeps a single unbroken token — an ISIN,
   * a reference number, a hyphen-free instrument name — from setting a floor
   * wider than the card and spilling out of it.
   */
  overflow-wrap: anywhere;
}

.panel__sub {
  font: var(--md-sys-typescale-body-small-font);
  color: var(--md-sys-color-on-surface-variant);
  margin: 0;
  min-inline-size: 0;
  overflow-wrap: anywhere;
}

/*
 * The label for a control that has no label of its own.
 *
 * A radio group, a checkbox list, a slider, a rating, a switch: the group needs
 * a name, and unlike a text field there is no floating label to carry it. These
 * used to borrow `.panel__sub`, which is a SUBTITLE — body-small, on-surface-
 * variant — and that inverted the hierarchy wherever the group also had help
 * text, because help text is body-medium in the same colour. "Investment
 * horizon" was rendering at 12px above a 14px "How long the money stays
 * invested": the label was the quietest thing in its own group, and the eye
 * landed on the sentence instead of on the name of the thing.
 *
 * Title-small is the smallest step that fixes it — same 14px as the help text,
 * so nothing grows, but 500 weight in full on-surface, so the label reads as
 * the head of the group and the sentence reads as a note under it.
 */
.field-label {
  font: var(--md-sys-typescale-title-small-font);
  color: var(--md-sys-color-on-surface);
  margin: 0;
  min-inline-size: 0;
  overflow-wrap: anywhere;
}

/*
 * The signed proposal's allocation, as five things to COMPARE.
 *
 * This was a flat `.row` of `.row`s: a chip, its percentage, the next chip, its
 * percentage, all at the same 8px. Two failures at once. Nothing grouped —
 * "54.0%" sat as close to "Fixed income" as to the "Equity" it belongs to — and
 * because the chips are all different widths (Cash 64px, Fixed income 117px),
 * the figures landed at five unrelated positions, so the one thing a reader
 * comes here to do, compare five percentages, needed five separate fixations.
 *
 * A grid of equal cells fixes both: each cell is one pair, so the pairs are
 * separated by a real gutter, and within a cell the figure is pushed to the
 * trailing edge — which is shared, because the cells are equal. The percentages
 * line up down a column of their own and the chips line up down another.
 *
 * (This is not the right-alignment `.dl--numeric` was deleted for. That pulled
 * a label away from its value across a wide free-form cell. Here the cell is
 * narrow and fixed, and the label and value are the two ends of one short row —
 * the ordinary "name ....... figure" line.)
 */
.alloc-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* 8px down, 28px across — the whole point of the rule. Everything here used
     to sit at a uniform 8px, so "54.0%" was as close to the "Fixed income"
     after it as to the "Equity" it belongs to, and the run read as ten loose
     items instead of five labelled figures. The gutter has to be visibly
     larger than the pair's own gap for the pairing to survive. */
  gap: var(--md-sys-spacing-gap-sm, 8px) 28px;
}

/*
 * An equal-column grid was the other way to do this, and it was worse. It does
 * line the figures up — but on a row of five, vertical alignment buys nothing
 * (they are side by side, not stacked), and pushing each figure to its cell's
 * trailing edge opened ~90px between a chip and its own percentage. The pairs
 * came apart to buy an alignment nobody reads. Tight pairs, wide gutters.
 */
.alloc-summary > * {
  display: inline-flex;
  align-items: center;
  gap: var(--md-sys-spacing-gap-sm, 8px);
  min-inline-size: 0;
}

.alloc-summary > * > :last-child {
  font-variant-numeric: tabular-nums;
}

/*
 * The code closes the step, underneath BOTH columns.
 *
 * Three placements were measured. At the foot of the right-hand column, under
 * ten instruments and a "N more" line, it was the one thing the advisor has to
 * DO on this step, put last in the secondary column. Moved into the summary
 * column and pinned down with `margin-block-start: auto`, the two columns
 * finished level — but the summary is ~300px shorter than the instrument list,
 * so all 300px opened up in the MIDDLE of the column and the code box read as
 * orphaned; a hole in the middle looks like something failed to load, which a
 * hole at the end does not.
 *
 * So it sits below the grid instead. Reading order is what is being signed,
 * then what it holds, then the box that signs it, and a full-width band under
 * two ragged columns closes the layout rather than competing with it.
 */
.sign-block {
  padding-block-start: var(--md-sys-spacing-inset-xl, 24px);
  /*
   * Takes the card's leftover height and centres in it.
   *
   * The summary above it is a fixed amount of content, and the card is as tall
   * as the instrument list beside it, so there is always slack underneath — the
   * code boxes were sitting in the top-left corner of a large empty area, which
   * made the one thing the advisor has to DO look like a footnote. `flex: 1`
   * claims that slack instead of leaving it, and `place-content: center` puts
   * the field in the middle of it on both axes, so the empty space frames the
   * field rather than trailing after it.
   */
  flex: 1 1 auto;
  display: grid;
  place-content: center;
}

/* --------------------------------------------------------------- kpi tiles */

/*
 * `min(210px, 100%)`, NOT a bare `210px`, and this is a real bug rather than
 * belt-and-braces.
 *
 * A bare floor in `minmax()` is a MINIMUM the track must honour, so in any
 * container narrower than the floor the track overflows instead of shrinking.
 * Measured on the analytics screen at 360px: the budget cards sit inside a
 * panel whose content box is 272px, the `.grid-2` floor is 340px, and the card
 * hung 68px past its panel. `min(floor, 100%)` caps the floor at the container,
 * so a narrow container gets one full-width track instead of an overflowing
 * one. Every auto-fit grid in this file needs it.
 */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: var(--md-sys-spacing-gap-md, 12px);
}

/*
 * TWO TILES ACROSS ON A PHONE, not one.
 *
 * At 360px the 210px floor resolves to a single column, so the four headline
 * figures became four full-width rows and pushed everything else a screen and
 * a half down — measured: the home screen was 5.1 phone screens tall. A KPI
 * tile is a label, a number and a hint; at ~160px it still holds a compact
 * currency figure without wrapping, and the block halves in height.
 */
@media (max-width: 599px) {
  .kpi-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--md-sys-spacing-gap-sm, 8px);
  }
}

.kpi {
  display: flex;
  flex-direction: column;
  gap: var(--md-sys-spacing-gap-xs, 4px);
  padding: var(--md-sys-spacing-inset-lg, 16px);
  min-inline-size: 0;
}

.kpi__label {
  font: var(--md-sys-typescale-label-medium-font);
  color: var(--md-sys-color-on-surface-variant);
  margin: 0;
}

.kpi__value {
  font: var(--md-sys-typescale-headline-small-font);
  color: var(--md-sys-color-on-surface);
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.kpi__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--md-sys-spacing-gap-sm, 8px);
  font: var(--md-sys-typescale-body-small-font);
  color: var(--md-sys-color-on-surface-variant);
}

.kpi__spark {
  margin-block-start: var(--md-sys-spacing-gap-xs, 4px);
}

/**
 * A TIGHTER KPI TILE ON A PHONE.
 *
 * Measured at 390px: four tiles at ~155px each, two rows, 310px — 37% of the
 * viewport spent on four numbers before the reader has seen an account. The
 * headline typescale and 16px of inset are right on a desktop tile that is
 * 300px wide; on a 185px one they are most of the box.
 *
 * The value drops one step to title-large and the inset to 12px, which brings
 * a tile to about 100px without shrinking the number below the thing a reader
 * is actually here to read. The sparkline is dropped rather than squeezed: at
 * 185px wide it is a 12-month curve in 160px, which is a texture rather than a
 * trend, and the tile beside it is not the place to read one.
 */
@media (max-width: 599px) {
  .kpi {
    gap: 2px;
    padding: var(--md-sys-spacing-inset-md, 12px);
  }

  .kpi__value {
    font: var(--md-sys-typescale-title-large-font);
  }

  .kpi__spark {
    display: none;
  }
}

/* ------------------------------------------------------------------ layout */

/*
 * Cards in a row share a height — `stretch`, not `start`.
 *
 * With `start` each card is only as tall as its own content, so two panels side
 * by side end at different heights and the row reads as ragged rather than as a
 * pair. `.panel` is `display: block` and its inner is a flex column, so the
 * extra height lands as card background under the content rather than
 * stretching the content itself.
 */
.grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  gap: var(--md-sys-spacing-gap-md, 12px);
  align-items: stretch;
}

.grid-3 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: var(--md-sys-spacing-gap-md, 12px);
  align-items: stretch;
}

/* A wide panel beside a narrow one — a chart and its legend, a table and its
   summary. Collapses to one column below the same 900px the rail does. */
.grid-wide {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(min(280px, 100%), 1fr);
  gap: var(--md-sys-spacing-gap-md, 12px);
  align-items: stretch;
}

@media (max-width: 899px) {
  .grid-wide {
    grid-template-columns: minmax(0, 1fr);
  }
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--md-sys-spacing-gap-md, 12px);
}

/*
 * A form's vertical rhythm, which is not a stack's.
 *
 * `.stack` is 12px, right for a run of blocks that belong together. A form row
 * is not that: each one carries a field AND its supporting text, so at 12px the
 * hint under one field sits closer to the NEXT field's label than to the field
 * it explains — the rows stop reading as rows. This is the same 24px the screen
 * uses between its sections, because a form row is a section of the form.
 */
/* There is no `--md-sys-spacing-gap-xl`, so `inset-xl` is the only 24px token
   that still tracks density (20px at -2, 16px at -3/-4). A hard 24px here would
   stay 24px while every field around it got shorter. */
.stack.form-stack {
  gap: var(--md-sys-spacing-inset-xl, 24px);
}

/*
 * The pickers fill their grid cell like every other field.
 *
 * `md-date-picker` and `md-time-picker` shrink-wrap their trigger field —
 * they publish no width prop and no width custom property — so in a two-column
 * form the date sat at 360px beside 698px neighbours and the row looked broken.
 * Their own trigger stretches once the host does.
 */
.form-stack md-date-picker,
.form-stack md-time-picker {
  inline-size: 100%;
  /* Lift `md-date-picker`'s 360px trigger ceiling inside the form, so the pair
     matches each other AND the rows above them — through the component's own
     public property now, rather than overriding its host from outside. The
     component keeps its default everywhere else. (Harmless on md-time-picker,
     which reads no such property.) */
  --md-date-picker-trigger-max-width: none;
}

.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--md-sys-spacing-gap-sm, 8px);
}

.row--end {
  justify-content: flex-end;
}

.row--between {
  justify-content: space-between;
}

/* ------------------------------------------------------------------- links */

.drill {
  color: var(--md-sys-color-primary);
  text-decoration: none;
  font-weight: 500;
  border-radius: var(--md-sys-shape-corner-extra-small);
}

.drill:hover {
  text-decoration: underline;
}

.drill:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: 2px;
}

/*
 * The badge and the dot both sit on the avatar RIM, and neither needs a
 * hand-tuned offset any more.
 *
 * This used to carry `--md-badge-offset-x: 8%` / `-y: -35%` — numbers found by
 * eye, correct for one avatar at one size and silently wrong at any other,
 * because a translate percentage is relative to the BADGE, not to the host it
 * is meant to hug. `md-badge` and `md-status-dot` now both take
 * `shape="circle"`, which anchors them with percentages of the HOST and is
 * therefore right at every size. The call sites say what the host is; nothing
 * here has to know its diameter.
 */

/*
 * A card that sits on the SAME surface the charts do.
 *
 * `md-card variant="outlined"` paints `--md-sys-color-surface` (#1C1B1F in
 * the dark scheme); `md-line-chart` and `md-pie-chart` paint
 * `surface-container-low` (#1D1B20). One shade apart, which is invisible in
 * isolation and obvious when a fact card sits directly beneath a chart on the
 * same panel.
 *
 * Set through the card's own documented custom property rather than a literal,
 * so it follows the theme, the accent preset and the light scheme with
 * everything else.
 */
.surface-card {
  --md-card-container-color: var(--md-sys-color-surface-container-low);
}

/*
 * A card whose whole content is one fact grid, sitting beside something taller.
 *
 * `.grid-wide` stretches its items, so this card is as tall as the chart next
 * to it — measured 260px against a 198px grid, which left 30px of dead space
 * under the last row while the rows themselves bunched at the top on a flat
 * 12px gap. Filling the card and DISTRIBUTING the rows spends that space where
 * it helps: the three rows breathe, and the block reads as deliberate rather
 * than as content that ran out.
 *
 * `align-content` and not `justify-content`: the grid's block axis is what
 * has slack. The row gap is a floor, so a card with no slack still separates
 * its rows properly.
 */
/* When the grid sits under a header row — chips, a title — the wrapper has to
   pass the card's spare height down to it, or the grid has nothing to
   distribute and the slack pools under the last row instead. */
.fact-card > .stack {
  flex: 1 1 auto;
  min-block-size: 0;
}

.fact-card .dl {
  flex: 1 1 auto;
  align-content: space-between;
  row-gap: var(--md-sys-spacing-gap-lg, 16px);
  column-gap: var(--md-sys-spacing-gap-xl, 24px);
}

/* --------------------------------------------------------------- data bits */

/*
 * Figures line up by place value or they cannot be compared, so every number
 * on every screen goes through one of these. `tabular-nums` gives each digit
 * the same advance width; `text-align: end` puts the units under the units.
 */
.num {
  font-variant-numeric: tabular-nums;
}

.num--end {
  font-variant-numeric: tabular-nums;
  text-align: end;
  display: block;
}

/*
 * `margin: 0` is not cosmetic here — it is what makes a caption belong to
 * something.
 *
 * `.muted` is nearly always a `<p>`, and a UA paragraph carries `margin: 1em 0`.
 * Inside a `.stack` — a flex column whose spacing comes from `gap` — those
 * margins do not collapse and are pure noise, added on BOTH sides. Measured on
 * the allocation step: the stack gap is 12px and the grid row gap is 12px, but
 * the caption under a weight field sat 26px below the field it explains and
 * 26px above the NEXT field's label. Perfectly equidistant, so it read as
 * belonging to neither, and the whole grid stopped grouping.
 *
 * `.panel__title`, `.panel__sub` and `.field-label` all zero their margins for
 * this reason; `.muted` was the one that got missed.
 */
.muted {
  color: var(--md-sys-color-on-surface-variant);
  margin: 0;
}

/*
 * A validation message for a control that cannot carry its own.
 *
 * `md-text-field`, `md-select`, `md-number-field`, `md-otp-field`,
 * `md-date-picker` and `md-time-picker` all hold their message inside
 * themselves, where it belongs and where the supporting-text line is already
 * reserved. `md-slider`, `md-rating`, `md-switch`, `md-transfer-list` and a
 * group of `md-checkbox`es have no such slot, so those messages are the app's
 * to draw — and they occupy the SAME line the hint was using, never an extra
 * one, or the control below moves every time validation fails.
 *
 * The colour is the `error` ROLE. `.pl-down` is the same red but it means "this
 * figure went down", and reusing it would put a P/L class on a validation
 * message.
 */
.field-error {
  margin: 0;
  color: var(--md-sys-color-error);
  min-inline-size: 0;
  overflow-wrap: anywhere;
}

/* The submit dialog's progress bar appears mid-flow when the send starts. The
   fact grid above it ends on a chip, and with no margin the wave began right
   under that chip and read as part of the last fact rather than as the state of
   the whole dialog. */
.submit-progress {
  display: block;
  margin-block-start: var(--md-sys-spacing-inset-xl, 24px);
}

/*
 * A filter-chip set sitting in a table container's `top` band, under the
 * toolbar.
 *
 * `.row` already supplies the flex, the wrap and the 8px gap the chip docs ask
 * for; all this adds is the alignment. The inline padding repeats the toolbar's
 * OWN expression rather than reaching for `--md-sys-spacing-inset-xl`, because
 * the two disagree away from density 0 — the toolbar runs 24/22/20/18/16 and
 * inset-xl runs 24/24/20/16/16, so a chip row built on the token would sit a
 * couple of pixels off the headline at three of the five rungs the dock can
 * write. There is no padding-block-start: the toolbar's own block padding is
 * already the gap between them.
 */
/**
 * ONE SCROLLING LINE PER FACET ON A PHONE, not a wrapped block.
 *
 * The statement has four facet rows — month, account, category, status — and
 * with 48px touch targets they wrapped into six lines of chips, about a third
 * of a screen of filters above the thing being filtered. A horizontally
 * scrollable chip row is the Material pattern for exactly this, and it keeps
 * each facet to one line whatever the locale does to the label lengths.
 *
 * `scroll-snap` so a swipe lands on a chip rather than halfway through one, and
 * the scrollbar is hidden because the chips are self-evidently a row that
 * continues — a visible bar under each of four rows is four bars of noise.
 * Scrolling stays reachable by keyboard: the row is focusable-by-content, and
 * tabbing to a chip scrolls it into view.
 */
/**
 * `md-search` CANNOT FIT A 360px PHONE at its defaults, and this is the fix.
 *
 * The component sets `min-inline-size: var(--md-search-container-min-inline-size, 360px)`.
 * On the most common small phone width that minimum is the entire viewport, so
 * the bar overflows its container by whatever padding sits around it —
 * measured at 37px inside this screen's filter panel. Nothing the host lays out
 * can shrink it, because a min-inline-size wins over the parent's width.
 *
 * Zeroing the floor through the component's own custom property is the
 * supported way out; the bar still fills its container through the
 * `inline-size: 100%` beside it. Worth raising upstream — a 360px floor makes
 * the component unusable at 360px in any app — but the default is not this
 * vertical's to change.
 */
@media (max-width: 719px) {
  md-search {
    --md-search-container-min-inline-size: 0;
  }
}

/**
 * A labelled facet: a caption over its chips.
 *
 * Four rows of outlined chips with nothing above them are four identical grey
 * bands, and a reader cannot tell the months from the accounts from the
 * categories without reading every chip. The caption is one line of
 * label-medium and it is what makes the panel scannable.
 */
.facet {
  display: flex;
  flex-direction: column;
  gap: var(--md-sys-spacing-gap-xs, 4px);
  min-inline-size: 0;
}

.facet__label {
  margin: 0;
  padding-inline: max(12px, calc(24px + var(--md-sys-density-scale, 0) * 2px));
  font: var(--md-sys-typescale-label-medium-font);
  color: var(--md-sys-color-on-surface-variant);
}

/* The count and the reset, on their own line under the facets. */
.facet-foot {
  padding-inline: max(12px, calc(24px + var(--md-sys-density-scale, 0) * 2px));
  padding-block-start: var(--md-sys-spacing-gap-xs, 4px);
}

/**
 * SCROLLING CHIP ROWS, AND THE FADE THAT SAYS SO.
 *
 * A row that simply stops at the container edge reads as clipped, not as
 * scrollable — the category row ended mid-word on "Entertainment" and looked
 * broken. `mask-image` fades the last 24px of a row that overflows, which is
 * the conventional signal that there is more in that direction.
 *
 * The fade is applied unconditionally rather than only when the row overflows,
 * because CSS cannot ask whether an element scrolls. On a row that FITS the
 * gradient still exists, so a last chip ending inside the final 24px would be
 * faded for no reason — checked across 640/700/719px in all three locales, and
 * the smallest margin on a fitting row is 40px, so it does not happen with this
 * content. It is a content-dependent assumption rather than a guarantee; if a
 * future facet lands a chip in that band, the fix is a `data-scrollable`
 * attribute set from a ResizeObserver, not a wider guess here.
 *
 * It is `mask-image` and not an overlay pseudo-element so it works on any
 * background — an overlay would have to guess the surface colour and would be
 * wrong inside a card.
 *
 * Logical properties throughout (`inline-end`, not `right`), so the fade is on
 * the correct side under `dir="rtl"`.
 */
.facet-row {
  --_fade: 24px;

  display: flex;
  flex-wrap: nowrap;
  gap: var(--md-sys-spacing-gap-sm, 8px);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* Both, and they do different jobs: `padding` keeps the chips off the port's
     edges, `scroll-padding` keeps a snapped chip off them too. */
  padding-inline: max(12px, calc(24px + var(--md-sys-density-scale, 0) * 2px));
  scroll-padding-inline: max(12px, calc(24px + var(--md-sys-density-scale, 0) * 2px));
  scrollbar-width: none;
  mask-image: linear-gradient(
    to var(--_fade-dir, right),
    #000 calc(100% - var(--_fade)),
    transparent 100%
  );
}

[dir='rtl'] .facet-row {
  --_fade-dir: left;
}

.facet-row::-webkit-scrollbar {
  display: none;
}

.facet-row > * {
  flex: 0 0 auto;
  scroll-snap-align: start;
}

/*
 * The wrapped layout comes back where there is room for it. Above the phone
 * breakpoint a facet has space for its chips on one or two lines, and wrapping
 * beats scrolling with a mouse — there is no swipe.
 */
@media (min-width: 720px) {
  .facet-row {
    flex-wrap: wrap;
    overflow-x: visible;
    mask-image: none;
  }
}

.strong {
  font-weight: 500;
}

/* A signed figure. The COLOUR is never the only carrier — the sign is in the
   text, because `signDisplay: 'exceptZero'` is what the formatters are given. */
.pl-up {
  color: var(--md-sys-color-success);
}

.pl-down {
  color: var(--md-sys-color-error);
}

.pl-flat {
  color: var(--md-sys-color-on-surface-variant);
}

/* ------------------------------------------------------------ fact  grids */

.dl {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: var(--md-sys-spacing-gap-md, 12px);
  margin: 0;
}

.dl > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
}

.dl dt {
  font: var(--md-sys-typescale-label-small-font);
  color: var(--md-sys-color-on-surface-variant);
}

.dl dd {
  font: var(--md-sys-typescale-body-large-font);
  margin: 0;
  font-variant-numeric: tabular-nums;
}

/*
 * THERE IS NO `.dl--numeric` ANY MORE, and the reason is worth keeping.
 *
 * It right-aligned both the term and the value of every fact in a grid, on the
 * theory that figures compare best flush-right. That is true INSIDE A TABLE,
 * where a column of numbers shares one edge and place values line up down the
 * page. A fact grid is not a table: each cell is its own island, so aligning to
 * the trailing edge lines the figures up against nothing and drags every label
 * away from the reader's eye. On the household return windows it put six
 * headings and their values hard against the right of a 1080px panel with a
 * river of empty space to their left.
 *
 * Right-alignment of numbers belongs to `md-table` — which does it properly,
 * per column, via `md-table-cell numeric`. `tabular-nums` on `.dl dd` above is
 * the part that was actually earning its keep and it stays.
 */

/* -------------------------------------------------------------- the when */

.when {
  color: var(--md-sys-color-on-surface-variant);
  font: var(--md-sys-typescale-body-small-font);
  white-space: nowrap;
}

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

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: var(--md-sys-spacing-inset-2xl, 40px) var(--md-sys-spacing-inset-lg, 16px);
  text-align: center;
}

.empty p {
  margin: 0;
}

.empty p:first-child {
  font: var(--md-sys-typescale-title-small-font);
  color: var(--md-sys-color-on-surface);
}

.empty p + p {
  font: var(--md-sys-typescale-body-medium-font);
  color: var(--md-sys-color-on-surface-variant);
}

/* ------------------------------------------------------------ feed ending */

.feed__end {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: var(--md-sys-spacing-inset-xl, 32px) 0;
  text-align: center;
}

.feed__end .material-symbols-outlined {
  font-size: 32px;
  color: var(--md-sys-color-primary);
}

.feed__more {
  display: flex;
  justify-content: center;
}

/* ---------------------------------------------------------- touch targets */

/**
 * A thumb needs 48px. Every one of these is smaller than that by default
 * because a pointer does not, and growing them unconditionally would put 48px
 * of padding around a 16px glyph on a desktop toolbar.
 */
@media (pointer: coarse) {
  .transport__button {
    inline-size: 44px;
    block-size: 44px;
  }
  .track-row__like,
  .clip {
    min-block-size: 44px;
  }
}

/* --------------------------------------------------------------- overlays */

/**
 * Anything sitting ON a picture.
 *
 * The backdrop is CONTENT, not a surface, so it cannot borrow the page's
 * colours — the same lesson Lyra learned twice. `inverse-surface` and
 * `inverse-on-surface` are the guaranteed-contrast pair in both themes, and the
 * `color-mix` keeps a little of the picture showing through without letting the
 * text lose its ground.
 */
.on-media {
  background: color-mix(in srgb, var(--md-sys-color-inverse-surface) 88%, transparent);
  color: var(--md-sys-color-inverse-on-surface);
  border-radius: var(--md-sys-shape-corner-small, 8px);
  padding: 2px 6px;
  font: var(--md-sys-typescale-label-small-font);
}


/* ------------------------------------------------- borrowed primitives */

/*
 * THREE CLASSES THAT WERE USED AND NEVER DEFINED.
 *
 * The shell and layout sections at the top of this file are copied from the
 * other verticals; these three live in each vertical's NAMED BLOCKS section, so
 * copying the shared half left them behind — and the screens went on using them
 * anyway. Nothing warned: an undefined class is not an error, it is a class
 * that does nothing, so `.stat-row` silently fell back to the user agent's
 * default `dd` indent and the event screen's figures sat 40px to the right of
 * their own labels.
 */

/**
 * A row of figures with their labels.
 *
 * EQUAL COLUMNS, NOT A FLEX ROW. As a flex row each pair is only as wide as its
 * own LABEL, so a short figure under a long word sits at the left of a wide
 * column and a long figure under a short word is cramped — the gaps are equal
 * and the numbers still look unevenly spaced, because what the eye measures is
 * the distance between the FIGURES. A grid of equal tracks puts them on a
 * regular rhythm whatever the labels do, which matters most in Romanian and
 * Arabic where the same words are very different lengths.
 *
 * The figure comes FIRST visually and the label second, via `order` — the
 * number is what the reader is here for. The DOM keeps `dt` before `dd`,
 * because that is the pairing a screen reader announces.
 */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: var(--md-sys-spacing-gap-md, 12px);
  margin: 0;
}

.stat-row > div {
  display: flex;
  flex-direction: column;
}

.stat-row dt {
  font: var(--md-sys-typescale-label-small-font);
  color: var(--md-sys-color-on-surface-variant);
  order: 2;
}

.stat-row dd {
  font: var(--md-sys-typescale-title-medium-font);
  color: var(--md-sys-color-on-surface);
  /* The UA gives `dd` a 40px inline start margin. Without this reset every
     figure in the app sits indented from the label above it. */
  margin: 0;
  order: 1;
}

/**
 * Announced, and occupying nothing.
 *
 * NOT `display: none` and NOT `visibility: hidden` — both take the text out of
 * the accessibility tree, which is the exact opposite of what this is for. The
 * clip-rect idiom is the one that leaves it readable to a screen reader.
 */
.visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/** The verified tick beside a name. Sized to the text it sits in, not to a
    fixed pixel value, so it tracks the heading it is part of. */
.verified {
  font-size: 1em;
  line-height: 1;
  color: var(--md-sys-color-primary);
}

/* ----------------------------------------------------------- named blocks */
/* ============================================================================
 * Everything below is Cygnus's own. The sections above are the shared shell.
 * ========================================================================= */

/* ------------------------------------------------------------- transport */
/*
 * THE BAR THAT OUTLIVES EVERY NAVIGATION.
 *
 * It is `position: sticky` at the bottom of the shell rather than `fixed`,
 * because a fixed bar is outside the document flow and would sit on top of the
 * last row of every screen — and the parity check measures document height, so
 * a bar that overlaps content makes five builds agree on a number that is wrong
 * in all five. Sticky keeps it in flow: the page is genuinely as tall as its
 * content plus the bar.
 */
.transport {
  position: sticky;
  inset-block-end: 0;
  z-index: 3;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 16px;
  padding: 10px 16px;
  border-block-start: 1px solid var(--md-sys-color-outline-variant);
  background: var(--md-sys-color-surface-container);
}

/*
 * THREE COLUMNS, AND THE MIDDLE ONE IS NOT CENTRED BY ACCIDENT. The outer two
 * are `1fr` so the controls sit in the true centre of the bar no matter how
 * long the track title on the left is. Laying this out with `justify-content:
 * space-between` moves the play button every time the title changes, which is
 * the single most distracting thing a transport can do.
 */
.transport__now {
  display: flex;
  align-items: center;
  gap: 12px;
  min-inline-size: 0;
}

.transport__art {
  inline-size: 44px;
  block-size: 44px;
  border-radius: var(--md-sys-shape-corner-small);
  flex: none;
  object-fit: cover;
}

.transport__text {
  min-inline-size: 0;
}

.transport__title {
  font: var(--md-sys-typescale-body-medium-font);
  color: var(--md-sys-color-on-surface);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.transport__artist {
  font: var(--md-sys-typescale-body-small-font);
  color: var(--md-sys-color-on-surface-variant);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.transport__controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.transport__buttons {
  display: flex;
  align-items: center;
  gap: 4px;
}

/*
 * The scrubber and its two readouts.
 *
 * `font-variant-numeric: tabular-nums` on the times, because a proportional
 * font gives "1" a narrower advance than "8" and the elapsed readout then
 * shifts the slider left and right as it counts. It is the same class of bug as
 * the centred controls above and it is far more visible.
 */
.transport__scrub {
  display: flex;
  align-items: center;
  gap: 10px;
  inline-size: min(460px, 42vw);
}

.transport__time {
  font: var(--md-sys-typescale-label-small-font);
  color: var(--md-sys-color-on-surface-variant);
  font-variant-numeric: tabular-nums;
  min-inline-size: 4ch;
  text-align: center;
}

.transport__slider {
  flex: 1;
  min-inline-size: 0;
}

.transport__side {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
}

.transport__volume {
  inline-size: 100px;
}

/*
 * Below the rail breakpoint the scrubber and the volume both go: the controls
 * and the title are what a phone-width transport is for.
 *
 * AND IT HAS TO CLEAR THE NAVIGATION BAR. `.shell__bar` is `position: fixed` at
 * the bottom with `z-index: 200`; this bar is sticky at `z-index: 3`, so at
 * compact widths the two occupied the same strip and the navigation covered the
 * transport completely — the app's one defining piece of chrome, invisible on a
 * phone, while every measurement said it was rendered and visible.
 *
 * The 80px is the bar's height and is not invented here: `.shell__main` already
 * reserves `calc(80px + …)` at this breakpoint for exactly the same reason, so
 * the two offsets move together. The dock's own height rides along through the
 * same custom property the bar uses.
 */
@media (max-width: 899px) {
  .transport {
    grid-template-columns: minmax(0, 1fr) auto;
    inset-block-end: calc(var(--awc-dock-height, 0px) + 80px);
  }
  .transport__scrub,
  .transport__side {
    display: none;
  }
}

/* --------------------------------------------------------------- the queue */

.queue-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-block: 6px;
}

.queue-row__index {
  font: var(--md-sys-typescale-label-small-font);
  color: var(--md-sys-color-on-surface-variant);
  font-variant-numeric: tabular-nums;
  min-inline-size: 2ch;
  text-align: end;
}

/* ----------------------------------------------------------- track rows */

.track-list {
  display: flex;
  flex-direction: column;
}

/*
 * A row is a GRID rather than a flex line, so the number, title, duration and
 * controls line up down the whole list. In flex each row sizes its own columns
 * and the durations form a ragged edge.
 */
.track-row {
  display: grid;
  grid-template-columns: 3ch minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border-radius: var(--md-sys-shape-corner-small);
}

/* With an album column the title gets the larger share, because a track title
   is the thing being scanned and an album name is context. */
.track-list[data-albums] .track-row {
  grid-template-columns: 3ch minmax(0, 1.6fr) minmax(0, 1fr) auto auto;
}

.track-row__album {
  font: var(--md-sys-typescale-body-small-font);
  color: var(--md-sys-color-on-surface-variant);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-inline-size: 0;
}

.track-row__album .link {
  color: inherit;
  text-decoration: none;
}

.track-row__album .link:hover,
.track-row__album .link:focus-visible {
  text-decoration: underline;
}

/* The album column is the first thing to go when the row runs out of room: the
   title, the duration and the two controls all have to stay. */
@media (max-width: 899px) {
  .track-list[data-albums] .track-row {
    grid-template-columns: 3ch minmax(0, 1fr) auto auto;
  }
  .track-row__album {
    display: none;
  }
}

.track-row:hover {
  background: var(--md-sys-color-surface-container-high);
}

/* The row that is loaded in the transport, marked on every screen that lists
   it — a reader should never have to look at the bar to know where they are. */
.track-row[data-current] {
  background: var(--md-sys-color-secondary-container);
}

.track-row[data-current] .track-row__title {
  color: var(--md-sys-color-on-secondary-container);
  font-weight: 600;
}

.track-row__index {
  font: var(--md-sys-typescale-body-small-font);
  color: var(--md-sys-color-on-surface-variant);
  font-variant-numeric: tabular-nums;
  text-align: end;
}

/*
 * THE TITLE AND THE ARTIST STACK, and this has to be said explicitly.
 *
 * The title is an `<a>` and the artist line is a `<span>` — both inline, so
 * without this they flow onto one line and render as `FerrousBjørk Sandø`, with
 * no space between them because there is no whitespace text node either. A
 * column here rather than `display: block` on each child, so the gap is one
 * declaration instead of a margin that has to be cancelled on the last row.
 */
.track-row__text {
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.track-row__title {
  font: var(--md-sys-typescale-body-medium-font);
  color: var(--md-sys-color-on-surface);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /*
   * NO UNDERLINE AT REST. `.link` underlines everything it is put on, which is
   * right for a link inside a sentence and wrong for one that IS a row in a
   * list: twelve underlined titles read as a page of hyperlinks rather than a
   * track list. The row's own hover background and the focus ring carry the
   * affordance, and the underline comes back on hover and on focus so it is
   * never absent for someone looking for it.
   */
  text-decoration: none;
}

.track-row__title:hover,
.track-row__title:focus-visible {
  text-decoration: underline;
}

.track-row__meta {
  font: var(--md-sys-typescale-body-small-font);
  color: var(--md-sys-color-on-surface-variant);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The artist link inside the meta line inherits the same treatment. */
.track-row__meta .link {
  color: inherit;
  text-decoration: none;
}

.track-row__meta .link:hover,
.track-row__meta .link:focus-visible {
  text-decoration: underline;
}

.track-row__time {
  font: var(--md-sys-typescale-body-small-font);
  color: var(--md-sys-color-on-surface-variant);
  font-variant-numeric: tabular-nums;
}

/*
 * A LIKED HEART IS FILLED, AND THE FILL COMES FROM THE FONT AXIS.
 *
 * This is the part that is not obvious. Material Symbols is a variable font
 * with a `FILL` axis, and the Outlined face this app loads renders BOTH
 * `favorite` and the legacy `favorite_border` as outlines — so swapping the
 * ligature name changes nothing at all. Two attempts went past before that was
 * the answer: `color="primary"`, an attribute `md-icon-button` does not have,
 * and then `selected-icon`, which changed the tint but not the shape. The heart
 * turned purple and stayed hollow, which reads as a hover rather than a state.
 *
 * `::part(icon)` is the component's own published hook, so this reaches into
 * the shadow root by invitation rather than by accident, and it is keyed on the
 * app's `data-liked` so the styling and the state cannot drift apart.
 */
.track-row__like[data-liked]::part(icon),
.track__like[data-liked]::part(icon) {
  font-variation-settings: 'FILL' 1;
}

/* ------------------------------------------------------------- peak bars */
/*
 * A track's sixteen loudness samples, drawn as a row of bars.
 *
 * THE HEIGHT COMES FROM A DATA ATTRIBUTE, not from a style attribute, for the
 * same policy reason the timeline exists in the shape it does: `style-src-attr
 * 'none'` refuses `style="height: 62%"`. The samples are rounded to the nearest
 * 10% and there are eleven rules below — coarser than the data, and the right
 * trade: a peak strip is a texture a reader glances at, and nobody can see the
 * difference between 62% and 60% in a 3px-wide bar.
 */
.peaks {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  block-size: 28px;
}

.peaks__bar {
  inline-size: 3px;
  border-radius: 1px;
  background: var(--md-sys-color-primary);
  opacity: 0.55;
}

.peaks__bar[data-h='0'] { block-size: 8%; }
.peaks__bar[data-h='1'] { block-size: 16%; }
.peaks__bar[data-h='2'] { block-size: 26%; }
.peaks__bar[data-h='3'] { block-size: 36%; }
.peaks__bar[data-h='4'] { block-size: 46%; }
.peaks__bar[data-h='5'] { block-size: 56%; }
.peaks__bar[data-h='6'] { block-size: 66%; }
.peaks__bar[data-h='7'] { block-size: 76%; }
.peaks__bar[data-h='8'] { block-size: 86%; }
.peaks__bar[data-h='9'] { block-size: 94%; }
.peaks__bar[data-h='10'] { block-size: 100%; }

/* ------------------------------------------------------------- the shelf */

.shelf {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 16px;
}

.shelf-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-decoration: none;
  color: inherit;
}

.shelf-card__art {
  inline-size: 100%;
  aspect-ratio: 1;
  border-radius: var(--md-sys-shape-corner-medium);
  object-fit: cover;
}

.shelf-card__art--wide {
  aspect-ratio: 2;
}

.shelf-card__title {
  font: var(--md-sys-typescale-body-medium-font);
  color: var(--md-sys-color-on-surface);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.shelf-card__meta {
  font: var(--md-sys-typescale-body-small-font);
  color: var(--md-sys-color-on-surface-variant);
}

/* ------------------------------------------------------------ release head */

.release-head {
  display: flex;
  gap: 24px;
  align-items: flex-end;
  flex-wrap: wrap;
}

.release-head__art {
  inline-size: 200px;
  aspect-ratio: 1;
  border-radius: var(--md-sys-shape-corner-large);
  object-fit: cover;
  flex: none;
}

.release-head__text {
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.release-head__title {
  font: var(--md-sys-typescale-headline-medium-font);
  color: var(--md-sys-color-on-surface);
  margin: 0;
}

/* ============================================================= THE TIMELINE */
/*
 * A CSS GRID WITH ONE COLUMN PER BAR, and this is the single most consequential
 * decision in the vertical.
 *
 * WHY IT IS NOT PIXELS. Every audio editor on the web positions a clip by
 * writing `left: 340px; width: 96px` onto it. The deployed content-security
 * policy for this showcase is `style-src-attr 'none'`, which refuses `style`
 * attributes outright — so that entire technique is unavailable, in all five
 * builds, and no amount of framework cleverness gets around it.
 *
 * WHAT WORKS INSTEAD. `grid-column` comes from a stylesheet rule, and a
 * stylesheet is allowed to do arithmetic the element is not. So a lane is a
 * grid with one column per bar, and a clip declares `data-start` and
 * `data-span`; the rules below place it. Two consequences, both worth having:
 *
 *   - Clips are QUANTISED to the bar. That is a real limitation, and an honest
 *     one for a showcase — most arrangement software quantises by default.
 *   - Five builds place clips identically because none of them is placing
 *     anything. They write two integers and the stylesheet does the rest, so
 *     there is no rounding for React and Angular to disagree about.
 *
 * The rules are generated up to `MAX_BARS` (96) in `types.ts`; a project longer
 * than that has no columns to sit in, which is why the fixture asserts its
 * length is one of `TIMELINE_BARS`.
 */

.timeline {
  overflow-x: auto;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  background: var(--md-sys-color-surface-container-low);
}

.timeline__inner {
  min-inline-size: max-content;
}

/*
 * ZOOM IS THE COLUMN WIDTH, and nothing else moves.
 *
 * Because every position is a grid column, zooming is one custom property on
 * the container — the clips, the ruler and the playhead all re-place
 * themselves, in step, with no arithmetic anywhere. A pixel-based timeline has
 * to multiply every offset by a scale factor and round each one, which is where
 * clips and ruler ticks drift apart at high zoom.
 */
.timeline[data-zoom='sm'] { --bar: 12px; }
.timeline[data-zoom='md'] { --bar: 24px; }
.timeline[data-zoom='lg'] { --bar: 44px; }

/*
 * EVERY ITEM IS PLACED EXPLICITLY, and that is what allows them to overlap.
 *
 * Grid only refuses to stack items it places ITSELF. An item with an explicit
 * `grid-row-start` and `grid-column-start` sits exactly where it is told, even
 * if something else is already there — which is the only way the playhead can
 * draw through a clip rather than being pushed past it.
 *
 * This was a real defect and it looked like a data problem: the playhead
 * occupied column 1 of an auto-placed lane, so every clip that began at bar 1
 * was bumped onto a second implicit row, and two lanes rendered at double
 * height with one clip floating below the rest.
 */
/*
 * ONE ROW, THE FULL HEIGHT OF THE LANE. `grid-auto-rows: 0` was the first
 * attempt at stopping a second row appearing and it collapsed every clip to a
 * sliver: the row it was placed in had no height to stretch into. Pinning the
 * single row to 100% both prevents the overflow row and gives the clips
 * something to fill.
 */
.lane,
.ruler {
  display: grid;
  grid-auto-rows: 100%;
}

.clip,
.playhead,
.ruler__tick {
  grid-row-start: 1;
}

/* One column per bar, at whatever the zoom says a bar is wide. */
.lane[data-bars='32'],
.ruler[data-bars='32'] { grid-template-columns: repeat(32, var(--bar)); }
.lane[data-bars='64'],
.ruler[data-bars='64'] { grid-template-columns: repeat(64, var(--bar)); }
.lane[data-bars='96'],
.ruler[data-bars='96'] { grid-template-columns: repeat(96, var(--bar)); }

.ruler {
  block-size: 28px;
  align-items: end;
  border-block-end: 1px solid var(--md-sys-color-outline-variant);
}

/* The positioning context for the label — see the note below. */
.ruler__tick {
  position: relative;
  border-inline-start: 1px solid var(--md-sys-color-outline-variant);
  block-size: 8px;
  align-self: end;
}

.ruler__tick[data-labelled] {
  block-size: 16px;
  border-inline-start-color: var(--md-sys-color-outline);
}

/*
 * The bar number sits OUTSIDE its own column, anchored to ITS OWN TICK.
 *
 * A label is wider than a 12px column at the smallest zoom, so it has to be
 * allowed to overflow its cell — hence `position: absolute`. The positioning
 * ancestor has to be the TICK: anchoring against the ruler, which is what this
 * did at first, put every label at the ruler's left edge in one illegible pile.
 */
.ruler__label {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 2px;
  font: var(--md-sys-typescale-label-small-font);
  color: var(--md-sys-color-on-surface-variant);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

.lane {
  block-size: 56px;
  align-items: stretch;
  border-block-end: 1px solid var(--md-sys-color-outline-variant);
  position: relative;
}

.lane:last-child {
  border-block-end: 0;
}

.clip {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 2px;
  cursor: grab;
  margin: 4px 1px;
  padding: 4px 6px;
  border-radius: var(--md-sys-shape-corner-small);
  border: 1px solid var(--md-sys-color-outline-variant);
  background: var(--md-sys-color-surface-container-highest);
  overflow: hidden;
  cursor: pointer;
  text-align: start;
  min-inline-size: 0;
}

.clip:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: 1px;
}

/*
 * `touch-action: none` IS LOAD-BEARING FOR THE DRAG.
 *
 * Without it a touch that starts on a clip is claimed by the browser's own
 * panning gesture — the timeline scrolls sideways and the pointer events stop
 * arriving, so the clip is left half-moved. It has to be declared in CSS
 * because by the time `pointerdown` fires the browser has already decided.
 */
.clip {
  touch-action: none;
  user-select: none;
}

/* While a gesture is running the clip leads rather than follows: it is above
   its neighbours and its cursor says which gesture is in progress. */
.clip[data-dragging] {
  z-index: 3;
  border-color: var(--md-sys-color-primary);
  box-shadow: var(--md-sys-elevation-2, 0 2px 6px rgb(0 0 0 / 0.4));
}

.clip[data-dragging='move'] {
  cursor: grabbing;
}

.clip[data-dragging='resize'] {
  cursor: ew-resize;
}

/*
 * The resize handle, on the clip's TRAILING edge.
 *
 * `inset-inline-end` rather than `right`, so it stays on the trailing edge in
 * Arabic — where the timeline runs the other way and a handle pinned to `right`
 * would sit at the clip's start, resizing from the wrong end.
 *
 * It is wider than it looks: 10px of target under a 2px line, because a 2px hit
 * area is unusable with a mouse and impossible with a finger.
 */
.clip__resize {
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  inline-size: 10px;
  cursor: ew-resize;
  touch-action: none;
}

.clip__resize::after {
  content: '';
  position: absolute;
  inset-block: 4px;
  inset-inline-end: 3px;
  inline-size: 2px;
  border-radius: 1px;
  background: var(--md-sys-color-outline);
  opacity: 0;
}

.clip:hover .clip__resize::after,
.clip[data-selected] .clip__resize::after,
.clip[data-dragging] .clip__resize::after {
  opacity: 1;
}

.clip[data-selected] {
  border-color: var(--md-sys-color-primary);
  background: var(--md-sys-color-primary-container);
}

.clip__label {
  font: var(--md-sys-typescale-label-small-font);
  color: var(--md-sys-color-on-surface);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.clip .peaks {
  block-size: 14px;
  gap: 1px;
}

.clip .peaks__bar {
  inline-size: 2px;
}

/*
 * The playhead.
 *
 * It is a grid item like everything else, in the column `playheadBar()`
 * returns, so it CANNOT drift from the clips by a rounding error — the failure
 * that makes a playhead appear to lead or trail the music. `pointer-events:
 * none` so it never swallows a click meant for the clip underneath it.
 */
.playhead {
  border-inline-start: 2px solid var(--md-sys-color-primary);
  block-size: 100%;
  pointer-events: none;
  z-index: 2;
}

/* ---- the generated column rules ---- */
/*
 * ONE RULE PER COLUMN, TWICE: a start and a span. Ninety-six of each, which is
 * the price of the policy and is paid once here rather than in five builds.
 *
 * `grid-column-start` is 1-based, matching `Clip.startBar` and the number the
 * ruler prints, so the data, the stylesheet and what a reader sees all count
 * the same way.
 */

[data-start='1'] { grid-column-start: 1; }
[data-start='2'] { grid-column-start: 2; }
[data-start='3'] { grid-column-start: 3; }
[data-start='4'] { grid-column-start: 4; }
[data-start='5'] { grid-column-start: 5; }
[data-start='6'] { grid-column-start: 6; }
[data-start='7'] { grid-column-start: 7; }
[data-start='8'] { grid-column-start: 8; }
[data-start='9'] { grid-column-start: 9; }
[data-start='10'] { grid-column-start: 10; }
[data-start='11'] { grid-column-start: 11; }
[data-start='12'] { grid-column-start: 12; }
[data-start='13'] { grid-column-start: 13; }
[data-start='14'] { grid-column-start: 14; }
[data-start='15'] { grid-column-start: 15; }
[data-start='16'] { grid-column-start: 16; }
[data-start='17'] { grid-column-start: 17; }
[data-start='18'] { grid-column-start: 18; }
[data-start='19'] { grid-column-start: 19; }
[data-start='20'] { grid-column-start: 20; }
[data-start='21'] { grid-column-start: 21; }
[data-start='22'] { grid-column-start: 22; }
[data-start='23'] { grid-column-start: 23; }
[data-start='24'] { grid-column-start: 24; }
[data-start='25'] { grid-column-start: 25; }
[data-start='26'] { grid-column-start: 26; }
[data-start='27'] { grid-column-start: 27; }
[data-start='28'] { grid-column-start: 28; }
[data-start='29'] { grid-column-start: 29; }
[data-start='30'] { grid-column-start: 30; }
[data-start='31'] { grid-column-start: 31; }
[data-start='32'] { grid-column-start: 32; }
[data-start='33'] { grid-column-start: 33; }
[data-start='34'] { grid-column-start: 34; }
[data-start='35'] { grid-column-start: 35; }
[data-start='36'] { grid-column-start: 36; }
[data-start='37'] { grid-column-start: 37; }
[data-start='38'] { grid-column-start: 38; }
[data-start='39'] { grid-column-start: 39; }
[data-start='40'] { grid-column-start: 40; }
[data-start='41'] { grid-column-start: 41; }
[data-start='42'] { grid-column-start: 42; }
[data-start='43'] { grid-column-start: 43; }
[data-start='44'] { grid-column-start: 44; }
[data-start='45'] { grid-column-start: 45; }
[data-start='46'] { grid-column-start: 46; }
[data-start='47'] { grid-column-start: 47; }
[data-start='48'] { grid-column-start: 48; }
[data-start='49'] { grid-column-start: 49; }
[data-start='50'] { grid-column-start: 50; }
[data-start='51'] { grid-column-start: 51; }
[data-start='52'] { grid-column-start: 52; }
[data-start='53'] { grid-column-start: 53; }
[data-start='54'] { grid-column-start: 54; }
[data-start='55'] { grid-column-start: 55; }
[data-start='56'] { grid-column-start: 56; }
[data-start='57'] { grid-column-start: 57; }
[data-start='58'] { grid-column-start: 58; }
[data-start='59'] { grid-column-start: 59; }
[data-start='60'] { grid-column-start: 60; }
[data-start='61'] { grid-column-start: 61; }
[data-start='62'] { grid-column-start: 62; }
[data-start='63'] { grid-column-start: 63; }
[data-start='64'] { grid-column-start: 64; }
[data-start='65'] { grid-column-start: 65; }
[data-start='66'] { grid-column-start: 66; }
[data-start='67'] { grid-column-start: 67; }
[data-start='68'] { grid-column-start: 68; }
[data-start='69'] { grid-column-start: 69; }
[data-start='70'] { grid-column-start: 70; }
[data-start='71'] { grid-column-start: 71; }
[data-start='72'] { grid-column-start: 72; }
[data-start='73'] { grid-column-start: 73; }
[data-start='74'] { grid-column-start: 74; }
[data-start='75'] { grid-column-start: 75; }
[data-start='76'] { grid-column-start: 76; }
[data-start='77'] { grid-column-start: 77; }
[data-start='78'] { grid-column-start: 78; }
[data-start='79'] { grid-column-start: 79; }
[data-start='80'] { grid-column-start: 80; }
[data-start='81'] { grid-column-start: 81; }
[data-start='82'] { grid-column-start: 82; }
[data-start='83'] { grid-column-start: 83; }
[data-start='84'] { grid-column-start: 84; }
[data-start='85'] { grid-column-start: 85; }
[data-start='86'] { grid-column-start: 86; }
[data-start='87'] { grid-column-start: 87; }
[data-start='88'] { grid-column-start: 88; }
[data-start='89'] { grid-column-start: 89; }
[data-start='90'] { grid-column-start: 90; }
[data-start='91'] { grid-column-start: 91; }
[data-start='92'] { grid-column-start: 92; }
[data-start='93'] { grid-column-start: 93; }
[data-start='94'] { grid-column-start: 94; }
[data-start='95'] { grid-column-start: 95; }
[data-start='96'] { grid-column-start: 96; }

[data-span='1'] { grid-column-end: span 1; }
[data-span='2'] { grid-column-end: span 2; }
[data-span='3'] { grid-column-end: span 3; }
[data-span='4'] { grid-column-end: span 4; }
[data-span='5'] { grid-column-end: span 5; }
[data-span='6'] { grid-column-end: span 6; }
[data-span='7'] { grid-column-end: span 7; }
[data-span='8'] { grid-column-end: span 8; }
[data-span='9'] { grid-column-end: span 9; }
[data-span='10'] { grid-column-end: span 10; }
[data-span='11'] { grid-column-end: span 11; }
[data-span='12'] { grid-column-end: span 12; }
[data-span='13'] { grid-column-end: span 13; }
[data-span='14'] { grid-column-end: span 14; }
[data-span='15'] { grid-column-end: span 15; }
[data-span='16'] { grid-column-end: span 16; }
[data-span='17'] { grid-column-end: span 17; }
[data-span='18'] { grid-column-end: span 18; }
[data-span='19'] { grid-column-end: span 19; }
[data-span='20'] { grid-column-end: span 20; }
[data-span='21'] { grid-column-end: span 21; }
[data-span='22'] { grid-column-end: span 22; }
[data-span='23'] { grid-column-end: span 23; }
[data-span='24'] { grid-column-end: span 24; }
[data-span='25'] { grid-column-end: span 25; }
[data-span='26'] { grid-column-end: span 26; }
[data-span='27'] { grid-column-end: span 27; }
[data-span='28'] { grid-column-end: span 28; }
[data-span='29'] { grid-column-end: span 29; }
[data-span='30'] { grid-column-end: span 30; }
[data-span='31'] { grid-column-end: span 31; }
[data-span='32'] { grid-column-end: span 32; }
[data-span='33'] { grid-column-end: span 33; }
[data-span='34'] { grid-column-end: span 34; }
[data-span='35'] { grid-column-end: span 35; }
[data-span='36'] { grid-column-end: span 36; }
[data-span='37'] { grid-column-end: span 37; }
[data-span='38'] { grid-column-end: span 38; }
[data-span='39'] { grid-column-end: span 39; }
[data-span='40'] { grid-column-end: span 40; }
[data-span='41'] { grid-column-end: span 41; }
[data-span='42'] { grid-column-end: span 42; }
[data-span='43'] { grid-column-end: span 43; }
[data-span='44'] { grid-column-end: span 44; }
[data-span='45'] { grid-column-end: span 45; }
[data-span='46'] { grid-column-end: span 46; }
[data-span='47'] { grid-column-end: span 47; }
[data-span='48'] { grid-column-end: span 48; }
[data-span='49'] { grid-column-end: span 49; }
[data-span='50'] { grid-column-end: span 50; }
[data-span='51'] { grid-column-end: span 51; }
[data-span='52'] { grid-column-end: span 52; }
[data-span='53'] { grid-column-end: span 53; }
[data-span='54'] { grid-column-end: span 54; }
[data-span='55'] { grid-column-end: span 55; }
[data-span='56'] { grid-column-end: span 56; }
[data-span='57'] { grid-column-end: span 57; }
[data-span='58'] { grid-column-end: span 58; }
[data-span='59'] { grid-column-end: span 59; }
[data-span='60'] { grid-column-end: span 60; }
[data-span='61'] { grid-column-end: span 61; }
[data-span='62'] { grid-column-end: span 62; }
[data-span='63'] { grid-column-end: span 63; }
[data-span='64'] { grid-column-end: span 64; }
[data-span='65'] { grid-column-end: span 65; }
[data-span='66'] { grid-column-end: span 66; }
[data-span='67'] { grid-column-end: span 67; }
[data-span='68'] { grid-column-end: span 68; }
[data-span='69'] { grid-column-end: span 69; }
[data-span='70'] { grid-column-end: span 70; }
[data-span='71'] { grid-column-end: span 71; }
[data-span='72'] { grid-column-end: span 72; }
[data-span='73'] { grid-column-end: span 73; }
[data-span='74'] { grid-column-end: span 74; }
[data-span='75'] { grid-column-end: span 75; }
[data-span='76'] { grid-column-end: span 76; }
[data-span='77'] { grid-column-end: span 77; }
[data-span='78'] { grid-column-end: span 78; }
[data-span='79'] { grid-column-end: span 79; }
[data-span='80'] { grid-column-end: span 80; }
[data-span='81'] { grid-column-end: span 81; }
[data-span='82'] { grid-column-end: span 82; }
[data-span='83'] { grid-column-end: span 83; }
[data-span='84'] { grid-column-end: span 84; }
[data-span='85'] { grid-column-end: span 85; }
[data-span='86'] { grid-column-end: span 86; }
[data-span='87'] { grid-column-end: span 87; }
[data-span='88'] { grid-column-end: span 88; }
[data-span='89'] { grid-column-end: span 89; }
[data-span='90'] { grid-column-end: span 90; }
[data-span='91'] { grid-column-end: span 91; }
[data-span='92'] { grid-column-end: span 92; }
[data-span='93'] { grid-column-end: span 93; }
[data-span='94'] { grid-column-end: span 94; }
[data-span='95'] { grid-column-end: span 95; }
[data-span='96'] { grid-column-end: span 96; }
/* ================================================================ THE MIXER */
/*
 * A BANK OF IDENTICAL STRIPS, which is a different problem from a form.
 *
 * Twelve strips of five controls is sixty interactive elements on one screen,
 * and the accessibility question stops being "does this control have a label"
 * and becomes "does its label say WHICH track it belongs to". A screen reader
 * announcing "Mute, button" twelve times is useless; every control here takes
 * its track's name into its accessible name, which the browser suite asserts.
 */

/*
 * THE STRIPS FILL THE ROW, then scroll.
 *
 * Fixed-width strips left a seven-track mixer as a huddle on the left of a
 * 1440px panel with two thirds of it empty — which reads as a broken layout
 * rather than a compact one. `flex: 1 0 116px` lets them GROW into the space
 * they have, and `max-inline-size` stops six tracks becoming six absurd
 * columns; `flex-shrink: 0` means twenty tracks overflow and scroll instead of
 * compressing into unusable slivers, which is the behaviour a real desk has.
 */
.mixer {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  padding-block-end: 8px;
}

.strip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  flex: 1 0 116px;
  max-inline-size: 184px;
  padding: 14px 10px;
  border-radius: var(--md-sys-shape-corner-medium);
  border: 1px solid var(--md-sys-color-outline-variant);
  background: var(--md-sys-color-surface-container-low);
}

/*
 * A STRIP THAT CANNOT BE HEARD SAYS SO, and it says so by dimming rather than
 * by hiding — the reader still needs to find it to un-silence it. This is keyed
 * on the DERIVED audibility from `audibleTracks()`, not on the mute flag, so a
 * track silenced by somebody else's solo dims too. That is the whole visual
 * payoff of getting the solo rule right: press solo on one strip and eleven
 * others visibly drop back.
 */
.strip[data-silent] {
  opacity: 0.45;
}

.strip__name {
  font: var(--md-sys-typescale-label-medium-font);
  color: var(--md-sys-color-on-surface);
  text-align: center;
  inline-size: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.strip__kind {
  font: var(--md-sys-typescale-label-small-font);
  color: var(--md-sys-color-on-surface-variant);
}

.strip__body {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 8px;
  block-size: 160px;
  inline-size: 100%;
}

/* A gap above the meter so it reads as belonging to the fader rather than
   floating between the fader and the decibel figure. */
.strip__meter,
.strip__pan {
  margin-block-start: 2px;
}

/*
 * A VERTICAL FADER, and there is NO CSS HERE THAT MAKES IT ONE.
 *
 * `md-slider` has `orientation="vertical"` and `full-height`, with its own
 * pointer handling for the block axis — so the strip asks the component for a
 * vertical slider rather than rotating a horizontal one. Rotating it with a
 * transform, or turning the axis with `writing-mode`, both produce a control
 * that LOOKS vertical while its hit area, focus ring and arrow keys stay
 * horizontal. The component was built for this; the stylesheet only has to give
 * it a parent with a definite height, which `.strip__body` does.
 */
.strip__fader {
  block-size: 100%;
}

/*
 * Full strip width, under the fader — see the note in `MixerScreen.tsx`.
 *
 * `--md-meter-height` rather than the `thickness` prop: the prop is what a
 * caller reaches for, but every build would have to pass the same number and
 * one of them would eventually pass a different one. The stylesheet is shared,
 * so putting it here means all five agree by construction. At the 4px default
 * the bar read as a hairline under a 160px fader and looked like a divider.
 */
.strip__meter {
  inline-size: 100%;
  --md-meter-height: 10px;
}

.strip__readout {
  font: var(--md-sys-typescale-label-small-font);
  color: var(--md-sys-color-on-surface-variant);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.strip__pan {
  inline-size: 100%;
}

.strip__buttons {
  display: flex;
  gap: 6px;
}

/*
 * A PRESSED MUTE OR SOLO GETS A FILLED PILL BEHIND IT.
 *
 * `aria-pressed` tells a screen reader; nothing told a sighted reader. The
 * component's `color` prop tints the glyph, which is not enough here: a soloed
 * strip is the only one at full opacity, so its glyphs are already the
 * brightest thing on the screen and a tint on top of that is invisible. A
 * filled background is the difference between "this button is available" and
 * "this button is on", and it is the same treatment `md-icon-button` gives its
 * own `variant="filled"`.
 */
.strip__mute[data-on],
.strip__solo[data-on] {
  border-radius: 50%;
  background: var(--md-sys-color-secondary-container);
}

.strip__solo[data-on] {
  background: var(--md-sys-color-primary-container);
}

/* ---------------------------------------------------------- studio header */

.studio-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  justify-content: space-between;
}

.studio-head__facts {
  display: flex;
  gap: 16px;
  align-items: center;
  flex-wrap: wrap;
}

.studio-head__tools {
  display: flex;
  gap: 6px;
  align-items: center;
}

/* The lane labels beside the timeline, aligned to the same 56px rows. */
.lanes {
  display: grid;
  grid-template-columns: 148px minmax(0, 1fr);
}

.lane-names {
  border-inline-end: 1px solid var(--md-sys-color-outline-variant);
}

.lane-name {
  block-size: 56px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding-inline: 10px;
  border-block-end: 1px solid var(--md-sys-color-outline-variant);
  font: var(--md-sys-typescale-label-medium-font);
  color: var(--md-sys-color-on-surface);
  min-inline-size: 0;
}

.lane-name:last-child {
  border-block-end: 0;
}

.lane-name__text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.lane-name[role='button'] {
  cursor: text;
}

.lane-name:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: -2px;
}

/*
 * A bare `<input>`, not `md-text-field`.
 *
 * The field has to fit a 56px lane row and sit exactly where the label was, and
 * a text field carries its own label slot, supporting text and 56px of minimum
 * height — so opening one would push every lane below it down by a row. This is
 * the one place in the app that styles a native control, and it borrows the
 * typography and colours from the same tokens.
 */
.lane-name__input {
  inline-size: 100%;
  min-inline-size: 0;
  font: var(--md-sys-typescale-label-medium-font);
  color: var(--md-sys-color-on-surface);
  background: var(--md-sys-color-surface-container-highest);
  border: 1px solid var(--md-sys-color-primary);
  border-radius: var(--md-sys-shape-corner-extra-small);
  padding: 4px 6px;
}

/* The ruler row has no label, but the names column must still start level with
   the first lane — an empty cell the same height as the ruler does it. */
.lane-names__pad {
  block-size: 28px;
  border-block-end: 1px solid var(--md-sys-color-outline-variant);
}

/* ------------------------------------------------------------ edit history */

.history-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-block: 6px;
  font: var(--md-sys-typescale-body-small-font);
  color: var(--md-sys-color-on-surface-variant);
}

/* An entry that has been undone is still listed — it is what redo will
   reapply — and reads as pending rather than as gone. */
.history-row[data-undone] {
  opacity: 0.5;
  text-decoration: line-through;
}

/* ---------------------------------------------------------- project cards */

.project-card {
  display: flex;
  gap: 14px;
  align-items: center;
  padding: 12px;
  border-radius: var(--md-sys-shape-corner-medium);
  border: 1px solid var(--md-sys-color-outline-variant);
  text-decoration: none;
  color: inherit;
}

.project-card[data-current] {
  border-color: var(--md-sys-color-primary);
  background: var(--md-sys-color-surface-container-high);
}

.project-card__art {
  inline-size: 64px;
  block-size: 64px;
  border-radius: var(--md-sys-shape-corner-small);
  object-fit: cover;
  flex: none;
}

.project-card__text {
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* ------------------------------------------------------------ artist rows */

.artist-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-block: 8px;
  text-decoration: none;
  color: inherit;
}

.artist-row__art {
  inline-size: 48px;
  block-size: 48px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}

/* Listening room: a editorial entry into the working transport and library. */
.music-home { gap: clamp(24px, 2.5vw, 40px); }
.listening-room {
  display: grid;
  grid-template-columns: minmax(0, 1.8fr) minmax(290px, 1fr);
  gap: 24px;
  min-inline-size: 0;
}
.listening-room__card {
  --md-card-padding: 0px;
  --md-card-container-shape: 28px;
  --md-card-container-color: var(--md-sys-color-surface-container-high);
  min-inline-size: 0;
}
.listening-room__feature {
  position: relative;
  min-block-size: 440px;
  padding: clamp(28px, 3vw, 44px);
  isolation: isolate;
  background: radial-gradient(ellipse at 92% 20%, color-mix(in srgb, var(--md-sys-color-primary-container) 80%, transparent), transparent 65%);
}
.listening-room__copy { position: relative; z-index: 2; max-inline-size: min(68%, 480px); }
.listening-room__eyebrow {
  display: block;
  margin-block-end: 20px;
  font: var(--md-sys-typescale-label-medium-font);
  letter-spacing: 0.14em;
  color: var(--md-sys-color-primary);
}
:dir(rtl) .listening-room__eyebrow { letter-spacing: normal; }
.listening-room h2 {
  margin: 0;
  max-inline-size: 12ch;
  font-size: clamp(36px, 3.6vw, 58px);
  font-weight: 650;
  line-height: 1.08;
  letter-spacing: -0.04em;
  text-wrap: balance;
}
:dir(rtl) .listening-room h2 { letter-spacing: normal; line-height: 1.4; }
.listening-room__copy > p { margin-block: 18px 24px; max-inline-size: 43ch; line-height: 1.7; color: var(--md-sys-color-on-surface-variant); }
.listening-room__modes { max-inline-size: 100%; display: flex; flex-wrap: wrap; gap: 6px; }
.listening-room__modes md-button { flex: 0 1 auto; }
.listening-room__session { margin-block: 24px; min-block-size: 100px; }
.listening-room__session h3 { margin: 0 0 6px; font: var(--md-sys-typescale-title-large-font); }
.listening-room__session p { margin: 0 0 12px; line-height: 1.55; color: var(--md-sys-color-on-surface-variant); }
.listening-room__facts { display: flex; flex-wrap: wrap; gap: 8px 18px; font: var(--md-sys-typescale-label-medium-font); color: var(--md-sys-color-on-surface-variant); }
.listening-room__play { inline-size: fit-content; }
.listening-room__demo { display: block; margin-block-start: 14px; color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-label-small-font); }
.listening-room__artwork { position: absolute; inline-size: 32%; max-inline-size: 265px; aspect-ratio: 0.7; inset-inline-end: 24px; inset-block-start: 60px; z-index: 1; pointer-events: none; }
.listening-room__cover { position: absolute; inline-size: 86%; aspect-ratio: 1; border-radius: 16px; overflow: hidden; box-shadow: 0 16px 36px color-mix(in srgb, var(--md-sys-color-shadow) 22%, transparent); border: 4px solid var(--md-sys-color-surface-container-highest); }
.listening-room__cover img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }
.listening-room__cover--0 { z-index: 3; inset-inline-start: 0; inset-block-start: 0; transform: rotate(-12deg); }
.listening-room__cover--1 { z-index: 2; inset-inline-end: -8px; inset-block-start: 26%; transform: rotate(9deg); }
.listening-room__cover--2 { z-index: 1; inset-inline-start: -5%; inset-block-start: 49%; transform: rotate(-5deg); }
.listening-room__record { position: absolute; inline-size: 75%; aspect-ratio: 1; border-radius: 50%; inset-block-end: -4%; inset-inline-end: -2%; background: repeating-radial-gradient(circle, var(--md-sys-color-surface-container-lowest) 0 3px, var(--md-sys-color-outline-variant) 4px 5px); border: 18px solid var(--md-sys-color-surface-container-lowest); }
.listening-room__record::after { content: ''; position: absolute; inset: 31%; border-radius: 50%; background: var(--md-sys-color-primary); }
.listening-queue { --md-card-padding: 24px; --md-card-container-shape: 28px; min-inline-size: 0; gap: 18px; }
.listening-queue__heading { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 8px; padding-block: 8px 16px; border-block-end: 1px solid var(--md-sys-color-outline-variant); }
.listening-queue__heading h3 { margin: 0; font: var(--md-sys-typescale-title-medium-font); }
.listening-queue__heading > span { color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-label-medium-font); }
.listening-queue__list { overflow-y: auto; max-block-size: 435px; min-inline-size: 0; scrollbar-gutter: stable; }
.listening-queue__row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 12px 4px; border-radius: 14px; }
.listening-queue__row[data-current] { background: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }
.listening-queue__track { min-inline-size: 0; display: grid; gap: 5px; }
.listening-queue__track strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: var(--md-sys-typescale-label-large-font); }
.listening-queue__track span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--md-sys-color-on-surface-variant); font: var(--md-sys-typescale-body-small-font); }
.music-home .shelf { grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 24px; }
.music-home .track-row { padding-block: 12px; }
.music-home .shelf-card { gap: 12px; }
.music-home .shelf-card__art { transition: transform 180ms ease; }
@media (hover: hover) { .music-home .shelf-card:hover .shelf-card__art { transform: translateY(-4px); } }
@media (max-width: 1250px) {
  .listening-room { grid-template-columns: minmax(0, 1fr); }
  .listening-room__copy { max-inline-size: min(65%, 530px); }
  .listening-room__artwork { max-inline-size: 300px; }
  .listening-queue__list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 20px; max-block-size: 235px; }
}
@media (max-width: 720px) {
  .listening-room { gap: 18px; }
  .listening-room__feature { min-block-size: 0; padding: 26px; }
  .listening-room__copy { max-inline-size: 100%; }
  .listening-room__artwork { display: none; }
  .listening-room h2 { max-inline-size: 15ch; font-size: clamp(32px, 7vw, 46px); }
  .listening-room__modes { gap: 4px; }
  .listening-queue { --md-card-padding: 18px; }
  .listening-queue__list { grid-template-columns: minmax(0, 1fr); max-block-size: 300px; }
  .music-home .shelf { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
}
@media (prefers-reduced-motion: reduce) { .music-home .shelf-card__art { transition: none; } }
