/**
 * App styles — the `@layer app` slot.
 *
 * The app's own structural and domain CSS. Sits below `theme`, so a theme can
 * restate a colour or radius without fighting these rules, and above
 * `components`, so a `ui-*` / `civ-*` element can still be placed in a layout.
 *
 * Tokens belong in `theme.css`; setting one here will not win.
 */

@layer app {
  ui-drawer m-library-drawer nav {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
  }

  ui-drawer m-library-drawer h2 {
    margin: 0 0 var(--s2);
    font-size: var(--f3);
  }

  ui-drawer m-library-drawer a {
    display: block;
    padding: var(--s2);
    color: var(--body);
    text-decoration: none;
  }

  ui-drawer m-library-drawer a[aria-current='page'] {
    background: var(--hover-tint);
    font-weight: var(--fw-semibold);
  }

  m-library-header {
    align-items: center;
    display: flex;
    gap: var(--s2);
    padding: var(--s3);
    width: 100%;
    box-sizing: border-box;
  }

  m-library-header h1 {
    flex: 1;
    font-size: var(--f3);
    margin: 0;
  }

  .library-menu-button,
  .library-header-action {
    align-items: center;
    background: transparent;
    border: none;
    box-shadow: none;
    color: var(--body);
    display: inline-flex;
    justify-content: center;
    min-width: 2rem;
    min-height: 2rem;
    padding: var(--s1);
    text-decoration: none;
  }

  .library-menu-icon,
  .library-menu-icon::before,
  .library-menu-icon::after {
    background: currentColor;
    content: '';
    display: block;
    height: 2px;
    position: relative;
    width: 1.25rem;
  }

  .library-menu-icon {
    position: relative;
  }

  .library-menu-icon::before {
    position: absolute;
    top: -6px;
  }
  .library-menu-icon::after {
    position: absolute;
    top: 6px;
  }

  m-library-header [data-actions] {
    display: flex;
    gap: var(--s2);
  }

  .library-header-action .mask-icon {
    width: 1.25rem;
    height: 1.25rem;
  }

  /* Civility's body grid uses a bare 1fr column, whose auto minimum won't
     shrink below main's min-content — wide content (e.g. the library sort
     control) pushes the whole page past the viewport on mobile instead of
     scrolling within its own overflow-x container */
  body > main {
    min-width: 0;
  }

  .offline-banner {
    padding: var(--s1) var(--s3);
    text-align: center;
    background: var(--primary);
    color: white;
    font-size: var(--f7);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
  }

  l-offline-banner {
    display: block;
    grid-area: banner;
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
  }

  /* Generic currentColor-masked feather icon (rendered by utils/icons.ts) */
  .mask-icon {
    display: inline-block;
    width: 1.1em;
    height: 1.1em;
    background: currentColor;
    -webkit-mask: var(--icon) center / contain no-repeat;
    mask: var(--icon) center / contain no-repeat;
    vertical-align: -0.2em;
    flex-shrink: 0;
  }

  .button .mask-icon {
    margin-right: var(--s1);
  }

  /* Bottom-nav icons: masked spans inherit currentColor (an <img> svg can't),
     so they stay legible in light and dark schemes */
  .nav-icon {
    width: 22px;
    height: 22px;
    background: currentColor;
    -webkit-mask: center / contain no-repeat;
    mask: center / contain no-repeat;
  }

  .nav-icon--music {
    -webkit-mask-image: url('/static/icons/music.svg');
    mask-image: url('/static/icons/music.svg');
  }

  .nav-icon--search {
    -webkit-mask-image: url('/static/icons/search.svg');
    mask-image: url('/static/icons/search.svg');
  }

  .nav-icon--voicemail {
    -webkit-mask-image: url('/static/icons/voicemail.svg');
    mask-image: url('/static/icons/voicemail.svg');
  }

  .nav-icon--settings {
    -webkit-mask-image: url('/static/icons/settings.svg');
    mask-image: url('/static/icons/settings.svg');
  }

  /* Civility dims inactive tabs to 50%, which reads as too faint here */
  ui-bottom-bar > a:not([aria-current='page']) {
    opacity: 0.75;
  }

  /* The header back arrow is an <img> inside ui-icon's shadow root, which
     can't inherit currentColor (black in every scheme) — hide it and repaint
     the button with a masked arrow that matches the header text color */ /* <l-player> sits as a direct body child inside civility's landmark grid.
  Without this it would be auto-placed into a named grid cell; contents
  removes it from the grid entirely (its bar is position: fixed anyway). */
  l-player {
    display: contents;
  }

  /* The <audio> element is rendered whether or not anything is queued (see
     l-player's render()), so under `contents` it would otherwise land in the
     grid as a bare child. Every UA already hides a controls-less audio; this
     just says so out loud. */
  l-player > audio {
    display: none;
  }

  /* Leave room for the fixed player bar. */
  body:has(l-player .player-bar) main {
    padding-bottom: calc(
      var(--player-height) + env(safe-area-inset-bottom, 0px) + var(--s3)
    );
  }

  /* ── Library ─────────────────────────────────────────────────────────── */

  .library,
  .settings,
  .track-page,
  .album-page,
  .search,
  .memos {
    max-width: var(--max-width-md);
    margin: 0 auto;
    padding: var(--s3);
  }

  .library-toolbar {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--s2) var(--s3);
    margin-bottom: var(--s3);
  }

  /* The 4-segment control scrolls rather than overflowing narrow screens */
  .library-sort {
    display: inline-flex;
    border: 1px solid var(--border);
    overflow-x: auto;
    max-width: 100%;
  }

  .library-sort button {
    border: none;
    border-radius: 0;
    background: transparent;
    padding: var(--s1) var(--s3);
    margin: 0;
    cursor: pointer;
    font: inherit;
    box-shadow: none;
    white-space: nowrap;
  }

  /* Civility's base button:hover lifts with translateY(-1px), which pulls a
     segment away from the pill's edge — keep these flush */
  .library-sort button:hover,
  .library-sort button:active {
    transform: none;
    opacity: 1;
    background: var(--hover-tint);
  }

  .library-sort button[data-active],
  .library-sort button[data-active]:hover {
    background: var(--primary);
    color: white;
  }

  /* Icon-only CTAs: no trailing gap, slightly larger glyph */
  .search-form button .mask-icon {
    margin-right: 0;
    width: 1.2em;
    height: 1.2em;
  }

  .library-empty {
    text-align: center;
    padding: var(--s5) var(--s3);
  }

  .offline-setting {
    display: flex;
    align-items: flex-start;
    gap: var(--s2);
  }

  .offline-setting span {
    display: grid;
    gap: var(--s1);
  }

  .offline-setting small {
    opacity: 0.7;
  }

  .library-hint {
    opacity: 0.7;
    font-size: var(--f6);
  }

  /* Artist index (library "Artists") */

  .artist-list {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .artist-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--s3);
    padding: var(--s2);
    text-decoration: none;
    color: inherit;
    border-bottom: 1px solid var(--border);
  }

  /* Tint overlays: enough contrast on any background, in either scheme */
  .artist-row:hover {
    background: var(--hover-tint);
  }

  .artist-row-name {
    font-weight: var(--fw-semibold);
  }

  .artist-row-meta {
    font-size: var(--f7);
    opacity: 0.6;
    white-space: nowrap;
  }

  /* Album cards (library "Albums" + artist page) */

  .album-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: var(--s3);
  }

  .album-card {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
    text-decoration: none;
    color: inherit;
  }

  .album-card-art {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    font-size: var(--f2);
  }

  .album-card-title {
    font-weight: var(--fw-semibold);
    line-height: var(--lh-tight);
    margin-top: var(--s1);
  }

  .album-card-meta {
    font-size: var(--f7);
    opacity: 0.65;
  }

  .album-art--empty,
  .player-art--empty,
  .track-art--empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--hover-tint);
    color: var(--primary);
  }

  /* The album page's empty art doubles as the art-menu toggle on real
     albums — everything else that reaches .album-art--empty is a plain
     <div>. */
  button.album-art--empty {
    border: none;
    padding: 0;
    cursor: pointer;
    transition: opacity var(--transition-base);
  }

  button.album-art--empty:hover {
    opacity: 0.8;
  }

  button.album-art--empty:disabled {
    cursor: default;
  }

  /* Album page */

  .album-hero {
    display: flex;
    gap: var(--s3);
    align-items: center;
    margin-bottom: var(--s3);
  }

  .album-art-wrap {
    position: relative;
    flex-shrink: 0;
  }

  .album-hero-art[tabindex='0'] {
    cursor: pointer;
  }

  /* Overlays the art itself rather than pushing content below it — a scrim
     dark enough for white text/icons regardless of the art's own colors or
     the active theme. */
  .art-cta-group {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
  }

  .art-cta-group .art-edit-link {
    color: inherit;
    opacity: 0.9;
  }

  .art-cta-group .art-edit-link:hover {
    opacity: 1;
  }

  .album-hero-art {
    width: 160px;
    height: 160px;
    object-fit: cover;
    flex-shrink: 0;
    font-size: var(--f1);
  }

  .album-hero-info h2 {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin: 0 0 var(--s1);
  }

  .album-hero-meta {
    margin: var(--s1) 0;
    opacity: 0.75;
    font-size: var(--f6);
  }

  .album-hero-meta a,
  .track-meta a {
    color: inherit;
  }

  .album-hero-info .button {
    margin-top: var(--s2);
  }

  .album-tracks {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .track-row {
    display: grid;
    grid-template-columns: var(--s4) 1.5rem 1fr auto;
    align-items: center;
    gap: var(--s2);
    padding: var(--s1);
  }

  /* Grid/flex children default to min-width:auto, which lets long titles
     blow rows open instead of truncating — force shrinkability */
  .track-row > *,
  .episode-row > *,
  .feed-result > *,
  .album-hero-info,
  .track-hero-info {
    min-width: 0;
  }

  .track-row:hover {
    background: var(--hover-tint);
  }

  .track-row[data-playing] {
    background: var(--playing-tint);
    box-shadow: inset 3px 0 0 var(--primary);
    font-weight: var(--fw-semibold);
  }

  .track-play {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: inherit;
    cursor: pointer;
    font-size: var(--f6);
    padding: var(--s1);
  }

  .track-no {
    font-size: var(--f7);
    opacity: 0.5;
    text-align: right;
  }

  .track-title {
    text-decoration: none;
    color: inherit;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .track-duration {
    font-size: var(--f7);
    opacity: 0.6;
    font-variant-numeric: tabular-nums;
  }

  .track-ctas {
    display: flex;
    align-items: start;
    gap: var(--s2);
  }

  /* ── Search + podcasts ───────────────────────────────────────────────── */

  /* `ui-search-field` lays its own form out as a row now; what is left here
     is the spacing below it. */
  .search-form {
    margin-bottom: var(--s3);
  }

  .search-form input {
    flex: 1;
    min-width: 0;
  }

  .search-hint {
    font-size: var(--f6);
    opacity: 0.7;
  }

  .track-row--simple {
    grid-template-columns: var(--s4) 1fr auto;
  }

  /* Album rows: whole row plays, trailing [...] opens the track page */
  .track-row--album {
    grid-template-columns: var(--s4) 1.5rem 1fr auto auto;
    cursor: pointer;
  }

  .feed-results {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .feed-result {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: var(--s2);
    padding: var(--s2) 0;
    border-bottom: 1px solid var(--border);
  }

  .feed-result-art {
    width: 52px;
    height: 52px;
    object-fit: cover;
    font-size: var(--f4);
  }

  .feed-result-info {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }

  .feed-result-title {
    font-weight: var(--fw-semibold);
  }

  .feed-result-meta {
    font-size: var(--f7);
    opacity: 0.65;
  }

  .feed-description {
    font-size: var(--f6);
    opacity: 0.8;
    margin-bottom: var(--s3);
  }

  .episode-list {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .episode-row {
    display: grid;
    grid-template-columns: var(--s4) 1fr;
    align-items: center;
    gap: var(--s2);
    padding: var(--s2) var(--s1);
    border-bottom: 1px solid var(--border);
  }

  .episode-row:hover {
    background: var(--hover-tint);
  }

  .episode-row[data-playing] {
    background: var(--playing-tint);
    box-shadow: inset 3px 0 0 var(--primary);
  }

  .episode-row[data-archived] {
    opacity: 0.55;
  }

  .episode-info {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }

  .episode-meta {
    font-size: var(--f7);
    opacity: 0.6;
  }

  /* ── Voice memos ─────────────────────────────────────────────────────── */

  .memo-record-wrap {
    display: flex;
    justify-content: center;
    margin: var(--s3) 0 var(--s4);
  }

  .memo-record {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    font: inherit;
    font-size: var(--f5);
    padding: var(--s2) var(--s3);
    border-radius: var(--br-full);
    border: none;
    background: var(--primary);
    color: white;
    cursor: pointer;
  }

  .memo-record[data-recording] {
    background: var(--error);
    font-variant-numeric: tabular-nums;
  }

  .memo-row {
    grid-template-columns: var(--s4) 1fr auto;
  }

  .sync-status {
    display: inline-flex;
    opacity: 0.55;
    font-size: 0.9em;
    vertical-align: baseline;
  }

  /* Podcast episode download/archive controls (l-episode-status) — bigger
     tap targets than the plain per-track sync badge, on both the track
     page's hero and the podcast page's episode list. */
  .sync-status--lg {
    font-size: 1.7em;
    margin-right: var(--s2);
  }

  .sync-status--lg:last-child {
    margin-right: 0;
  }

  .sync-status--online-only,
  .sync-status--downloaded {
    opacity: 1;
    cursor: pointer;
  }

  .sync-status--online-only {
    color: var(--primary-dull);
  }

  .sync-status--downloaded {
    color: var(--primary);
  }

  .sync-status--errored {
    opacity: 1;
    color: var(--error);
  }

  /* A permanently-failed upload (oversized/invalid content) is still
     offline-only in shape — local, not backed up — but won't resolve on
     its own the way "queued"/"uploading" will. */
  .sync-status--upload-failed {
    opacity: 1;
    color: var(--error);
  }

  /* The downloaded badge swaps its glyph for x-circle on hover — a CTA to
     drop the local copy while leaving the cloud copy alone. */
  .sync-status--downloaded .sync-status-icon--hover {
    display: none;
  }

  .sync-status--downloaded:hover .sync-status-icon--default {
    display: none;
  }

  .sync-status--downloaded:hover .sync-status-icon--hover {
    display: inline-flex;
  }
  @keyframes sync-status-spin {
    to {
      transform: rotate(360deg);
    }
  }

  .sync-status--downloading .mask-icon {
    animation: sync-status-spin 0.8s linear infinite;
  }

  /* ── Playlists ───────────────────────────────────────────────────────── */

  .playlist-hero {
    margin-bottom: var(--s3);
  }

  .playlist-hero h2 {
    margin: 0 0 var(--s1);
  }

  .playlist-track {
    grid-template-columns: var(--s4) 1fr auto auto;
  }

  .playlist-controls {
    display: inline-flex;
    gap: var(--s1);
  }

  .playlist-controls button[disabled] {
    opacity: 0.3;
    cursor: default;
  }

  .playlist-add {
    display: block;
    margin-top: var(--s2);
    max-width: 14rem;
    margin-bottom: var(--s2);
    padding: var(--s2);
    height: var(--f1);
  }

  .memos-empty {
    text-align: center;
  }

  /* ── Track page ──────────────────────────────────────────────────────── */

  .track-hero {
    display: flex;
    gap: var(--s3);
    margin-bottom: var(--s3);
  }

  .track-art-wrap {
    position: relative;
    flex-shrink: 0;
    /* .track-hero has no align-items of its own (default stretch) — without
       this the wrap stretches to the info column's height, and the overlay
       scrim (which fills the wrap via inset: 0) stretches with it. */
    align-self: flex-start;
  }

  .track-art {
    width: 160px;
    height: 160px;
    object-fit: cover;
    flex-shrink: 0;
    font-size: var(--f1);
    cursor: pointer;
  }

  button.track-art--empty {
    border: none;
    padding: 0;
    transition: opacity var(--transition-base);
  }

  button.track-art--empty:hover {
    opacity: 0.8;
  }

  .art-edit-link {
    font-size: var(--f7);
    opacity: 0.7;
    cursor: pointer;
  }

  .art-edit-link:hover {
    opacity: 1;
  }

  /* The art CTAs are plain <button>s styled as quiet text links. */
  button.art-edit-link {
    border: none;
    background: none;
    padding: 0;
    font: inherit;
  }

  button.art-edit-link:disabled {
    cursor: default;
    opacity: 0.5;
  }

  .track-hero-info h2 {
    margin: 0 0 var(--s1);
  }

  .track-rename {
    border: none;
    background: transparent;
    color: inherit;
    font-size: var(--f6);
    opacity: 0.5;
    cursor: pointer;
    padding: var(--s1);
    vertical-align: middle;
  }

  .track-rename:hover {
    opacity: 1;
    transform: none;
    background: var(--hover-tint);
  }

  .track-rename-input {
    font: inherit;
    font-size: var(--f4);
    font-weight: var(--fw-semibold);
    width: 100%;
    box-sizing: border-box;
    margin: 0 0 var(--s1);
  }

  .track-meta {
    margin: var(--s1) 0;
    opacity: 0.75;
    font-size: var(--f6);
  }

  .track-play-btn {
    margin-top: var(--s2);
    height: var(--f1);
  }

  .track-stats {
    font-size: var(--f6);
    opacity: 0.75;
    border-block: 1px solid var(--border);
    padding: var(--s2) 0;
  }

  .note-list {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .note {
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    gap: var(--s2);
    align-items: baseline;
    padding: var(--s2) 0;
    border-bottom: 1px solid var(--border);
  }

  .note-at {
    border: none;
    background: var(--primary-dull);
    color: var(--primary);
    padding: 0 var(--s2);
    font-size: var(--f7);
    font-variant-numeric: tabular-nums;
    cursor: pointer;
  }

  .note-body {
    margin: 0;
    white-space: pre-wrap;
  }

  .note-date {
    font-size: var(--f7);
    opacity: 0.5;
    white-space: nowrap;
  }

  .note-delete {
    border: none;
    background: transparent;
    cursor: pointer;
    opacity: 0.4;
    font-size: var(--f5);
  }

  .note-delete:hover {
    opacity: 1;
  }

  .note-form textarea {
    width: 100%;
    box-sizing: border-box;
    margin-top: var(--s2);
  }

  .note-form-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--s2);
    margin-top: var(--s1);
  }

  .note-pin {
    border: 1px dashed var(--border);
    background: transparent;
    padding: var(--s1) var(--s2);
    font-size: var(--f7);
    cursor: pointer;
  }

  .note-pin[data-active] {
    border-style: solid;
    background: var(--primary-dull);
  }

  .notes-empty {
    opacity: 0.6;
    font-size: var(--f6);
  }

  .notes-panel-header {
    display: flex;
    justify-content: flex-end;
    margin-bottom: var(--s2);
  }

  .note-add-btn {
    border: none;
    background: transparent;
    color: inherit;
    opacity: 0.6;
    cursor: pointer;
    padding: var(--s1);
  }

  .note-add-btn:hover {
    opacity: 1;
  }

  .tab-loading,
  .tab-empty {
    opacity: 0.6;
    font-size: var(--f6);
  }

  .tab-error {
    color: var(--error);
    font-size: var(--f6);
  }

  .tab-loading .mask-icon {
    animation: sync-status-spin 0.8s linear infinite;
  }

  .catalog-info {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--s1) var(--s3);
    margin: 0;
  }

  .catalog-info dt {
    opacity: 0.6;
    font-size: var(--f7);
  }

  .catalog-info dd {
    margin: 0;
  }

  .lyrics-text {
    white-space: pre-wrap;
    font-family: inherit;
  }

  .tab-edit-actions {
    display: flex;
    gap: var(--s2);
    margin-bottom: var(--s2);
  }

  .lyrics-edit {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
  }

  .track-danger {
    margin-top: var(--s4);
  }

  .button-danger {
    border: 1px solid var(--error);
    color: var(--error);
    background: transparent;
    padding: var(--s1) var(--s3);
    cursor: pointer;
  }

  /* ── Player bar ──────────────────────────────────────────────────────── */

  /* Theme-following bar: civility tokens, so it matches the app (and its
     dark scheme) instead of imposing its own */
  l-player .player-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    min-height: var(--player-height);
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      'art info controls'
      'art seek seek';
    align-items: center;
    column-gap: var(--s3);
    row-gap: var(--s1);
    padding: var(--s2) var(--s3)
      calc(var(--s2) + env(safe-area-inset-bottom, 0px));
    background: color-mix(in srgb, var(--primary) 20%, var(--background));
    border-top: 1px solid var(--border);
    box-shadow: 0 -4px 12px color-mix(in srgb, var(--primary) 8%, transparent);
    z-index: var(--z-fixed);
  }

  .player-art {
    grid-area: art;
    width: 52px;
    height: 52px;
    object-fit: cover;
  }

  l-player .player-art--empty {
    font-size: var(--f4);
  }

  .player-info {
    grid-area: info;
    display: flex;
    flex-direction: column;
    min-width: 0;
  }

  .player-title {
    color: inherit;
    text-decoration: none;
    font-weight: var(--fw-semibold);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .player-artist {
    font-size: var(--f7);
    opacity: 0.7;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .player-controls {
    grid-area: controls;
    display: flex;
    align-items: center;
    gap: var(--s1);
  }

  .player-controls button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: inherit;
    cursor: pointer;
    font-size: var(--f5);
    padding: var(--s1) var(--s2);
  }

  .player-controls button:hover {
    background: var(--primary-dull);
    transform: none;
    opacity: 1;
  }

  .track-play:hover {
    transform: none;
  }

  .player-toggle {
    font-size: var(--f4);
  }

  /* Play-through mode reads as "repeat: off" — same glyph, clearly faded */
  .player-mode {
    position: relative;
    font-size: var(--f6);
    opacity: 0.35;
  }

  .player-mode-badge {
    position: absolute;
    top: 0;
    right: 0;
    font-size: 0.55rem;
    font-weight: var(--fw-bold);
    line-height: 1;
  }

  /* Stop-after-track: the "1" sits inside the (hollow) square glyph */
  .player-mode[data-mode='single'] .player-mode-badge {
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.5rem;
  }

  .player-mode[data-active] {
    opacity: 1;
    background: var(--primary-dull);
    color: var(--primary);
  }

  .player-seek {
    grid-area: seek;
    display: flex;
    align-items: center;
    gap: var(--s2);
  }

  .player-seek input[type='range'] {
    flex: 1;
    accent-color: var(--primary);
  }

  .player-time {
    font-size: var(--f7);
    opacity: 0.7;
    font-variant-numeric: tabular-nums;
  }

  /* ── Settings ────────────────────────────────────────────────────────── */

  .settings section {
    margin-bottom: var(--s5);
  }

  /* `h2` is here for `ui-settings-about`, whose app-name heading is the top
     of its page rather than a section title; every other heading on a settings
     page is an `h3`, and they should read the same size. */
  .settings h2,
  .settings h3 {
    font-size: var(--f4);
    font-weight: var(--fw-semibold);
    margin-bottom: var(--s3);
  }

  .settings section > p {
    opacity: 0.6;
    margin-bottom: var(--s3);
    font-size: var(--f5);
  }

  .settings-hint {
    font-size: var(--f6);
    opacity: 0.75;
  }

  .settings-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
  }

  /* `.settings-select`, `.settings-toggle-row` and the `.settings-nav-*`
     family were all written here before `_patterns.css` existed; the
     framework ships every one of them now (`@layer components`), and the
     `civ-settings-*` scenes render them. Deleted rather than kept as
     overrides — the framework's versions have been tuned since, and an
     app-layer copy would out-rank a designer's theme for no reason.

     Music's own squared-off look survives regardless: `--br-*` are all
     `0rem` in theme.css, which is exactly the kind of thing a token is for. */

  /* <label class="button"> file-input triggers: match real buttons */
  .settings-actions label.button {
    cursor: pointer;
    white-space: nowrap;
  }

  .settings-actions label.button:has(input:disabled) {
    opacity: 0.5;
    pointer-events: none;
  }

  .settings-folder-list {
    list-style: none;
    margin: 0 0 var(--s2);
    padding: 0;
  }

  .settings-folder {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding: var(--s1) 0;
  }

  .settings-folder-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .settings-progress,
  .settings-report {
    font-size: var(--f6);
  }

  .settings-error {
    color: var(--error);
    font-size: var(--f6);
  }

  .settings-errors {
    font-size: var(--f7);
  }

  /* ── Action buttons ───────────────────────────────────────────────────
     `civ-sync-input` and `civ-store-import` render their own Authorize /
     Sync now / Import / Export / Delete buttons with class="action" (and
     "link" for the dismissive ones) — plain hooks with no built-in look,
     styled per-app like the rest of Civility's component classes. */ /* ── civ-sync-input component ───────────────────────────────────────── */

  civ-sync-input form {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
  }

  civ-sync-input label {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
    cursor: default;
    border: none;
    padding: 0;
    font-weight: var(--fw-normal);
    background: none;
  }

  civ-sync-input label span {
    font-size: var(--f6);
    opacity: 0.7;
  }

  civ-sync-input label input {
    width: 100%;
    box-sizing: border-box;
  } /* Sized to content once it's sitting in a row of buttons, rather than
  stretching full-width like the initial "Authorize" submit. */
  /* ── civ-sync-state component ───────────────────────────────────────── */ /* ── civ-store-import component ─────────────────────────────────────── */

  civ-store-import div {
    display: flex;
    gap: var(--s3);
    margin-bottom: var(--s3);
  }

  ui-pwa-version,
  ui-pwa-install {
    display: block;
    opacity: 0.7;
    font-size: var(--f7);
  }

  /* ── Name dialog (l-name-dialog) ─────────────────────────────────────── */

  .name-dialog form {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    min-width: min(20rem, 80vw);
  }

  .name-dialog label {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
  }

  .name-dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--s2);
  }

  /* ── Note dialog (routes/track.ts) ───────────────────────────────────── */

  .note-dialog form {
    min-width: min(24rem, 80vw);
  }

  .note-dialog-buttons {
    display: flex;
    gap: var(--s2);
  }
  @media (pointer: coarse) {
    .track-play,
    .player-controls button,
    .track-rename {
      padding: var(--s2);
    }
  }
  @media (max-width: 480px) {
    /* Heroes: smaller art so title + actions keep room */
    .album-hero,
    .track-hero {
      gap: var(--s2);
    }

    .album-hero-art {
      width: 110px;
      height: 110px;
      font-size: var(--f2);
    }

    .track-art {
      width: 96px;
      height: 96px;
      font-size: var(--f2);
    }

    /* Playlist rows: duration cedes its column to the reorder controls */
    .playlist-track {
      grid-template-columns: var(--s4) 1fr auto;
    }

    .playlist-track .track-duration {
      display: none;
    }

    /* Player bar: tighter around the same controls */
    l-player .player-bar {
      column-gap: var(--s2);
      padding: var(--s2) var(--s2)
        calc(var(--s2) + env(safe-area-inset-bottom, 0px));
    }
  }
}
