/* =========================================================================
   studio.css — the design-system layer (S1)
   =========================================================================
   Loaded after design-tokens.css, components.css and the legacy style.css, so
   it is the last word on the shell, the card language and the shared list /
   control vocabulary.  Page-specific layers (dashboard.css, memory.css,
   chat.css, settings.css) load after this file and only add.

   Rules for this file:
   * every colour and every length is a token — this is the one place the
     studio layer's own values are allowed to be declared, and they live in
     design-tokens.css under "Studio layer";
   * no `outline: none` — the focus ring stays visible everywhere;
   * no page markup is referenced by id, only by the classes named below, so a
     page can be rebuilt without touching the shell.

   Class contract (page authors may rely on these):
     shell      .app-header .app-brand .app-header-actions .app-icon-btn
                .app-status-pill .app-header-back .page-shell .section-bar
     surfaces   .card .ui-card .card-header .card-title .card-body
     numbers    .bento .bento-hero .bento-grid .stat-tile .stat-tile-value
     controls   .segmented .segment .ui-btn .ui-badge
     lists      .ranked-list .ranked-item .ranked-avatar .ranked-bar-track
     feedback   .skeleton .empty-state .ui-banner a
   ========================================================================= */

/* ── Page canvas ──────────────────────────────────────────────────────── */

body {
  background-color: var(--color-surface);
  background-image: var(--page-gradient);
  background-attachment: fixed;
  background-repeat: no-repeat;
  -webkit-tap-highlight-color: transparent;
}

::selection {
  background: var(--color-accent-dim);
  color: var(--color-text);
}

* {
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

*::-webkit-scrollbar {
  width: var(--space-xs);
  height: var(--space-xs);
}

*::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: var(--radius-full);
}

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

/* Skip link — unchanged behaviour, restyled so it reads as part of the app. */
.skip-link {
  position: absolute;
  left: var(--space-md);
  top: var(--space-md);
  z-index: var(--z-overlay);
  padding: var(--space-xs) var(--space-md);
  background: var(--color-accent);
  color: var(--color-text-inverse);
  border-radius: var(--radius-full);
  font-weight: var(--font-weight-semibold);
  transform: translateY(-200%);
  transition: transform var(--transition-ease-out);
}

.skip-link:focus {
  transform: translateY(0);
}

/* ── Shell layout ─────────────────────────────────────────────────────── */

/* Prevent horizontal layout wobble: enforce overflow-x: hidden and width: 100% */
html,
body {
  overflow-x: hidden;
  width: 100%;
  min-height: 100vh;
  min-height: 100dvh;
}

.app-layout {
  display: flex;
  min-height: 100vh;
  min-height: 100dvh;
  width: 100%;
  background: transparent;
}

.main-content {
  flex: 1 1 auto;
  min-width: 0;
  margin-left: var(--sidebar-width);
  max-width: none;
  padding: var(--space-md) var(--space-md)
    calc(var(--tabbar-height) + var(--space-2xl) + env(safe-area-inset-bottom, 0));
}

/* Anti-clipping: expand main-content bottom clearance when mini-player is active */
body:has(#mini-player:not([hidden])) .main-content,
body.has-active-player .main-content,
body.has-mini-player .main-content {
  padding-bottom: calc(
    var(--tabbar-height) + var(--mini-player-clearance) + var(--space-2xl) + env(safe-area-inset-bottom, 0)
  );
}

@media (min-width: 768px) {
  body:has(#mini-player:not([hidden])) .main-content,
  body.has-active-player .main-content,
  body.has-mini-player .main-content {
    padding-bottom: calc(
      var(--mini-player-clearance) + var(--space-3xl)
    );
  }
}

/* The content column: one width, one vertical rhythm, every page. */
.page-shell {
  width: 100%;
  /* The header is sticky and in flow, so this is the gap between it and the
     first row of content — it used to come from main's padding, which the
     header's negative top margin cancelled. */
  padding-top: var(--space-xs);
  max-width: var(--content-max);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

/* A page that owns its own height (the chat surface) opts out of the rhythm. */
.page-shell--full {
  max-width: none;
  gap: 0;
  flex: 1 1 auto;
  min-height: 0;
}

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

/* Page-level enter: hardware-accelerated 60fps spring transitions */
@keyframes studio-rise {
  from {
    opacity: 0;
    transform: translate3d(0, var(--space-xs), 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

.main-content > * {
  animation: studio-rise var(--transition-spring, 360ms cubic-bezier(0.34, 1.56, 0.64, 1)) both;
  will-change: transform, opacity;
}

/* Staggered list entrances using CSS custom property --i */
@keyframes studio-stagger-in {
  from {
    opacity: 0;
    transform: translate3d(0, var(--space-xs), 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

.stagger-item,
[data-stagger] > *,
.bento-grid > *,
.ranked-list > li {
  animation: studio-stagger-in 320ms cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: calc(var(--i, 0) * 45ms);
  will-change: transform, opacity;
}

@media (prefers-reduced-motion: reduce) {
  .main-content > *,
  .stagger-item,
  [data-stagger] > *,
  .bento-grid > *,
  .ranked-list > li {
    animation: none;
    will-change: auto;
  }
}

/* ── Header bar ───────────────────────────────────────────────────────── */

.app-header {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: var(--header-height);
  /* Bleed to the viewport edges, but never upward: a negative top margin made a
     61px header reserve only 45px of flow space, so the section bar underneath
     it started 16px inside the header and its active pill painted over the
     brand on any device where the glass went translucent. */
  margin: 0 calc(var(--space-md) * -1) 0;
  padding: var(--space-xs) var(--space-md);
  padding-top: calc(var(--space-xs) + env(safe-area-inset-top));
  background: var(--glass-bg);
  border-bottom: var(--hairline) solid var(--glass-border);
  backdrop-filter: blur(var(--glass-blur)) saturate(140%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(140%);
}

.app-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-width: 0;
  min-block-size: var(--tap-min);
  color: var(--color-text);
  text-decoration: none;
}

.app-brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--space-xl);
  block-size: var(--space-xl);
  flex: 0 0 auto;
  border-radius: var(--radius-md);
  background: var(--accent-gradient);
  color: var(--color-text-inverse);
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-xs);
  letter-spacing: -0.02em;
  box-shadow: var(--shadow-accent);
}

.app-brand-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: var(--line-height-tight);
}

.app-brand-name {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-brand-sub {
  font-size: var(--font-size-2xs);
  color: var(--color-text-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.app-header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-left: auto;
}

.app-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: var(--tap-min);
  min-block-size: var(--tap-min);
  padding: 0;
  color: var(--color-text-secondary);
  background: transparent;
  border: var(--hairline) solid transparent;
  border-radius: var(--radius-full);
  transition:
    color var(--transition),
    background var(--transition),
    border-color var(--transition);
}

.app-icon-btn:hover {
  color: var(--color-text);
  background: var(--surface-tint);
  border-color: var(--glass-border);
}

.app-icon-btn.is-active {
  color: var(--color-accent);
  background: var(--color-accent-dim);
}

.app-header-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-block-size: var(--tap-min);
  padding-inline: var(--space-sm) var(--space-md);
  margin-inline-start: calc(var(--space-xs) * -1);
  color: var(--color-text-secondary);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  border-radius: var(--radius-full);
  text-decoration: none;
}

.app-header-back:hover {
  color: var(--color-text);
  background: var(--surface-tint);
}

/* The ingest rail, stated in one pill. */
.app-status-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-block-size: var(--tap-min);
  padding: 0 var(--space-sm);
  background: var(--surface-tint);
  border: var(--hairline) solid var(--glass-border);
  border-radius: var(--radius-full);
  color: var(--color-text-secondary);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition:
    background var(--transition),
    color var(--transition);
}

.app-status-pill:hover {
  background: var(--surface-tint-strong);
  color: var(--color-text);
}

.app-status-dot {
  inline-size: var(--space-xs);
  block-size: var(--space-xs);
  border-radius: var(--radius-full);
  background: var(--color-text-muted);
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 12%, transparent);
}

.app-status-pill[data-state="ok"] {
  color: var(--color-success);
}

.app-status-pill[data-state="ok"] .app-status-dot {
  background: var(--color-success);
  animation: status-pulse 2.4s ease-in-out infinite;
}

.app-status-pill[data-state="warn"] {
  color: var(--color-warning);
}

.app-status-pill[data-state="warn"] .app-status-dot {
  background: var(--color-warning);
}

.app-status-pill[data-state="unknown"] {
  color: var(--color-text-muted);
}

@keyframes status-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}

/* ── Page heading ─────────────────────────────────────────────────────── */

.page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-sm);
  margin: 0;
  flex-wrap: wrap;
}

.page-header-left {
  min-width: 0;
}

.page-title {
  margin: 0;
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  letter-spacing: -0.035em;
  line-height: var(--line-height-tight);
}

.page-subtitle {
  margin: var(--space-2xs) 0 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  line-height: var(--line-height-normal);
}

.page-header-right {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  flex-wrap: wrap;
}

.eyebrow {
  margin: 0;
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* The section switcher: quiet pills, accent only on the current one. */
.section-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
  margin: 0;
  padding-bottom: var(--space-xs);
  overflow-x: auto;
  scrollbar-width: none;
}

.section-bar::-webkit-scrollbar {
  display: none;
}

.section-link {
  background: transparent;
  border: var(--hairline) solid transparent;
  color: var(--color-text-muted);
  font-size: var(--font-size-2xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding-inline: var(--space-sm);
  /* A hair over the 44px floor: 44.0 measured in a browser can land at
     43.99, which fails the tap-target audit for no visual gain. */
  min-block-size: var(--tap-comfortable);
}

.section-link:hover {
  background: var(--surface-tint);
  color: var(--color-text);
}

.section-link.is-current,
.section-link.is-current:hover {
  background: var(--color-accent-dim);
  border-color: color-mix(in srgb, var(--color-accent) 30%, transparent);
  color: var(--nav-active-text);
}

.section-signout {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-left: auto;
}

/* ── Cards ────────────────────────────────────────────────────────────── */

.card,
.ui-card {
  position: relative;
  background: var(--glass-bg);
  border: var(--hairline) solid var(--glass-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  overflow: hidden;
  transition:
    border-color var(--transition),
    background var(--transition),
    transform var(--transition-ease-out);
}

.ui-card-hoverable:hover,
.card:hover {
  border-color: color-mix(in srgb, var(--color-accent) 26%, var(--glass-border));
}

.ui-card-hoverable:hover {
  transform: translateY(calc(var(--space-3xs) * -1));
}

/* A hairline of brand light along the top edge of a primary card. */
.card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  block-size: var(--hairline);
  background: var(--hero-gradient);
  opacity: 0.9;
  pointer-events: none;
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  flex-wrap: wrap;
  padding: var(--space-md) var(--space-md) 0;
  border-bottom: 0;
}

.card-title {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  letter-spacing: -0.01em;
  color: var(--color-text);
}

.card-subtitle {
  margin: var(--space-3xs) 0 0;
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

.card-body {
  padding: var(--space-md);
}

.dashboard-section {
  margin: 0;
}

/* ── Numbers: the bento ───────────────────────────────────────────────── */

/* Mobile: a hero stat plus a two-column grid — never a column of five rows. */
.bento {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-xs);
}

.bento-hero {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-lg);
  background:
    var(--hero-gradient),
    var(--glass-bg-strong);
  border: var(--hairline) solid var(--glass-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

/* The hero number is the one thing the eye must land on first.  It used to
   compute to the same 29px/700 as the page title, so "Total Plays" and
   "Dashboard" competed and the number lost.  A genuine display tier: clearly
   above the title at every width, tightening as it grows. */
.bento-hero .stat-tile-value {
  font-size: clamp(2.5rem, 7vw, 3.5rem);
  line-height: 0.95;
  letter-spacing: -0.035em;
}

.bento-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-xs);
}

.stat-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-sm);
  min-width: 0;
  background: var(--glass-bg);
  border: var(--hairline) solid var(--glass-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition:
    border-color var(--transition),
    background var(--transition);
}

.stat-tile:hover {
  border-color: color-mix(in srgb, var(--color-accent) 22%, var(--glass-border));
}

.stat-tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--space-xl);
  block-size: var(--space-xl);
  border-radius: var(--radius-md);
  background: var(--color-accent-dim);
  color: var(--color-accent);
  flex: 0 0 auto;
}

.stat-tile-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  min-width: 0;
}

.stat-tile-value {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  letter-spacing: -0.03em;
  line-height: var(--line-height-tight);
  font-variant-numeric: tabular-nums;
}

.stat-tile-label {
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: var(--line-height-tight);
}

.stat-tile-foot {
  font-size: var(--font-size-2xs);
  color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums;
}

/* Legacy stat cards (other pages) inherit the tile language. */
.stats-bar {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-xs);
  margin: 0;
}

.stat-card {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
  padding: var(--space-sm);
  background: var(--glass-bg);
  border: var(--hairline) solid var(--glass-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.stat-card:hover {
  border-color: color-mix(in srgb, var(--color-accent) 22%, var(--glass-border));
}

/* An odd count of stats leaves a hole in the last row of the 2-column phone
   grid; let that last tile fill the row instead of looking unfinished.
   (Desktop uses auto-fit, which collapses unused tracks and needs no rule.) */
@media (max-width: 767px) {
  .stats-bar > :last-child:nth-child(odd) {
    grid-column: span 2;
  }
}

.stat-icon {
  inline-size: var(--space-xl);
  block-size: var(--space-xl);
  border-radius: var(--radius-md);
  background: var(--surface-tint);
  color: var(--color-accent);
}

.stat-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.stat-value {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.stat-label {
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  /* Two lines before an ellipsis: "CACHE HIT RA…" is not a label. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: var(--line-height-tight);
}

/* ── Controls ─────────────────────────────────────────────────────────── */

.ui-btn,
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-block-size: calc(var(--tap-min) + var(--hairline));
  padding: var(--space-2xs) var(--space-md);
  border: var(--hairline) solid transparent;
  border-radius: var(--radius-full);
  font-family: inherit;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.01em;
  line-height: var(--line-height-tight);
  text-decoration: none;
  cursor: pointer;
  transition:
    background var(--transition),
    color var(--transition),
    border-color var(--transition),
    transform var(--transition-fast);
}

.ui-btn:active,
.btn:active {
  transform: scale(0.98);
}

.ui-btn-primary,
.btn-primary {
  background: var(--color-accent);
  /* The accent fill decides the ink: light-theme green is dark, dark-theme green
     is bright, and --color-text-inverse on either measured 3.2:1 / 2.2:1. */
  color: var(--color-on-accent);
  box-shadow: var(--shadow-accent);
}

.ui-btn-primary:hover:not(:disabled),
.btn-primary:hover:not(:disabled) {
  background: var(--color-accent-hover);
}

.ui-btn-secondary,
.btn-secondary {
  background: var(--surface-tint);
  border-color: var(--glass-border);
  color: var(--color-text);
}

.ui-btn-secondary:hover:not(:disabled),
.btn-secondary:hover:not(:disabled) {
  background: var(--surface-tint-strong);
}

.ui-btn-ghost,
.btn-icon {
  background: transparent;
  color: var(--color-text-secondary);
}

.ui-btn-ghost:hover:not(:disabled),
.btn-icon:hover {
  background: var(--surface-tint);
  color: var(--color-text);
}

.ui-btn-danger,
.btn-danger {
  background: var(--color-danger-dim);
  color: var(--color-danger);
  border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
}

.ui-btn-danger:hover:not(:disabled) {
  background: var(--color-danger);
  color: var(--color-text-inverse);
}

/* Icon-only controls get the comfortable size for the same sub-pixel reason. */
.btn-icon,
.stats-refresh,
#dashboard-refresh-btn,
.chat-send-btn,
.chat-stop,
.modal-close {
  min-inline-size: var(--tap-comfortable);
  min-block-size: var(--tap-comfortable);
}

/* Legacy form controls (the explorer, the chat bar) get the same floor. */
.explore-select,
.explore-date-input {
  min-block-size: var(--tap-comfortable);
}

.ui-btn:disabled,
.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Segmented control: pills inside a track, animated sliding pill indicator */
.segmented,
.period-selector {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  padding: var(--space-3xs);
  background: color-mix(in srgb, var(--color-surface-input) 70%, transparent);
  border: var(--hairline) solid var(--glass-border);
  border-radius: var(--radius-full);
  flex-shrink: 0;
  overflow-x: auto;
  scrollbar-width: none;
}

.segmented::-webkit-scrollbar,
.period-selector::-webkit-scrollbar {
  display: none;
}

.segment,
.period-btn {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: var(--tap-min);
  /* Short labels ("All") collapsed to 38.3px at 1440; the pill needs the same
     floor on the inline axis that it already had on the block axis. */
  min-inline-size: var(--tap-min);
  padding: 0 var(--space-sm);
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-text-secondary);
  font-family: inherit;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  cursor: pointer;
  transition:
    color var(--transition),
    background var(--transition);
}

.segmented-indicator {
  position: absolute;
  top: var(--space-3xs);
  bottom: var(--space-3xs);
  left: 0;
  border-radius: var(--radius-full);
  background: var(--color-accent);
  box-shadow: var(--shadow-accent);
  pointer-events: none;
  z-index: 0;
  transition:
    transform var(--transition-pill, 260ms cubic-bezier(0.34, 1.56, 0.64, 1)),
    width var(--transition-pill, 260ms cubic-bezier(0.34, 1.56, 0.64, 1));
  transform: translate3d(0, 0, 0);
  will-change: transform, width;
}

.segment:hover,
.period-btn:hover {
  background: var(--surface-tint);
  color: var(--color-text);
}

.segment.is-active,
.period-btn.active {
  background: var(--color-accent);
  color: var(--color-text-inverse);
  font-weight: var(--font-weight-semibold);
  box-shadow: var(--shadow-accent);
}

/* When sliding pill indicator is active, keep active button transparent so indicator shines through */
.segmented.has-indicator .segment.is-active,
.period-selector.has-indicator .period-btn.active {
  background: transparent;
  color: var(--color-text-inverse);
  box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
  .segmented-indicator {
    transition: none;
    will-change: auto;
  }
}

.segmented-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Badges */
.ui-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  padding: var(--space-3xs) var(--space-xs);
  border-radius: var(--radius-full);
  background: var(--surface-tint);
  border: var(--hairline) solid var(--glass-border);
  color: var(--color-text-secondary);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.04em;
  text-transform: none;
  white-space: nowrap;
}

.ui-badge-accent {
  background: var(--color-accent-dim);
  border-color: color-mix(in srgb, var(--color-accent) 26%, transparent);
  color: var(--color-accent);
}

.ui-badge-warning {
  background: var(--color-warning-dim);
  border-color: color-mix(in srgb, var(--color-warning) 26%, transparent);
  color: var(--color-warning);
}

.ui-badge-danger {
  background: var(--color-danger-dim);
  border-color: color-mix(in srgb, var(--color-danger) 26%, transparent);
  color: var(--color-danger);
}

.ui-badge-type-behavioral-pattern {
  background: color-mix(in srgb, var(--color-purple) 18%, transparent);
  border-color: color-mix(in srgb, var(--color-purple) 34%, transparent);
  color: var(--color-purple);
}

.ui-badge-type-observation {
  background: color-mix(in srgb, var(--color-info) 18%, transparent);
  border-color: color-mix(in srgb, var(--color-info) 34%, transparent);
  color: var(--color-info);
}

/* ── Ranked lists ─────────────────────────────────────────────────────── */

.ranked-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ranked-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: calc(var(--tap-min) + var(--space-xs));
  padding: var(--space-xs) var(--space-xs);
  border-radius: var(--radius-lg);
  transition: background var(--transition);
}

.ranked-item + .ranked-item {
  border-top: var(--hairline) solid
    color-mix(in srgb, var(--border-color) 55%, transparent);
}

.ranked-item:hover {
  background: var(--surface-tint);
}

.ranked-item:hover + .ranked-item {
  border-top-color: transparent;
}

/* A name that links to the artist page is still a thumb target: the row's
   inline anchors carry the 44px minimum themselves, so a short name is not a
   smaller tap than a long one. */
.ranked-item a {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--tap-min);
  min-inline-size: var(--tap-min);
  color: inherit;
  text-decoration: none;
}

.ranked-item a:hover .ranked-name {
  text-decoration: underline;
}

.rank-avatar,
.ranked-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  flex: 0 0 auto;
  border-radius: var(--radius-full);
  background: var(--avatar-1);
  color: var(--color-text);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  overflow: hidden;
}

.ranked-avatar[data-tint="2"] {
  background: var(--avatar-2);
}
.ranked-avatar[data-tint="3"] {
  background: var(--avatar-3);
}
.ranked-avatar[data-tint="4"] {
  background: var(--avatar-4);
}
.ranked-avatar[data-tint="5"] {
  background: var(--avatar-5);
}
.ranked-avatar[data-tint="6"] {
  background: var(--avatar-6);
}

.rank-number {
  inline-size: var(--space-md);
  flex: 0 0 auto;
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.ranked-info {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  /* The name keeps at least half the row even when the metrics are wide: a
     nine-character artist must not wrap mid-word in a 60px column. */
  flex: 1 1 auto;
  min-width: 45%;
}

/* Two lines are allowed; ellipsis only after that. */
.ranked-name {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  letter-spacing: -0.01em;
  line-height: var(--line-height-tight);
  white-space: normal;
  overflow-wrap: break-word;
  hyphens: none;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.ranked-metrics {
  display: flex;
  align-items: baseline;
  gap: var(--space-2xs);
  flex: 0 0 auto;
  font-size: var(--font-size-2xs);
  color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ranked-plays,
.ranked-hours {
  /* Explicit, not inherited: the row's counters were landing at 10.92px by
     inheritance from a max()-based token, under the readable floor. */
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
}

.ranked-hours::before {
  content: "•";
  margin-inline-end: var(--space-2xs);
  color: var(--color-text-muted);
}

/* The bar lives under the row, full width, never clipped at the viewport. */
.ranked-bar-track {
  position: relative;
  display: block;
  inline-size: 100%;
  block-size: var(--space-2xs);
  margin-top: var(--space-2xs);
  border-radius: var(--radius-full);
  background: var(--meter-track);
  overflow: hidden;
}

.ranked-bar {
  display: block;
  block-size: 100%;
  border-radius: inherit;
  background: var(--accent-gradient);
  transition: inline-size var(--transition-slow);
}

/* Legacy absolute bar (kept in case an old partial still emits it). */
.ranked-bar-wrapper {
  position: static;
  inline-size: 100%;
  block-size: var(--space-3xs);
  margin-top: var(--space-2xs);
  background: var(--meter-track);
  border-radius: var(--radius-full);
  overflow: hidden;
}

.ranked-sub {
  font-size: var(--font-size-2xs);
  color: var(--color-text-muted);
  white-space: normal;
}

/* ── Now playing ──────────────────────────────────────────────────────── */

.now-playing-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  background:
    var(--hero-gradient),
    transparent;
}

.now-playing-heading {
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.now-playing-now {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-width: 0;
}

.now-playing-art,
.album-art-placeholder {
  inline-size: var(--space-3xl);
  block-size: var(--space-3xl);
  border-radius: var(--radius-md);
  object-fit: cover;
  flex: 0 0 auto;
  background: var(--surface-tint-strong);
  box-shadow: var(--shadow-md);
}

.now-playing-artist,
.now-playing-subtitle {
  font-size: var(--font-size-xs);
  color: var(--color-text-secondary);
}

.now-playing-title {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  letter-spacing: -0.02em;
}

/* ── Bottom navigation ────────────────────────────────────────────────── */

/* Hidden on desktop; the sidebar is the navigation there. */
.tabbar {
  display: none;
}

@media (min-width: 768px) {
  .main-content {
    margin-left: var(--sidebar-width);
    padding: var(--space-lg) var(--space-xl) var(--space-3xl);
  }

  .app-header {
    margin: 0 calc(var(--space-xl) * -1) 0;
    padding-inline: var(--space-xl);
    min-height: calc(var(--header-height) + var(--space-2xs));
  }

  .bento {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  /* Five stats, three or five columns — never a hole. auto-fit collapses the
     tracks a page does not use, so the last row is always full. */
  .stats-bar {
    grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  }

  .bento-hero {
    grid-column: span 2;
  }

  .page-title {
    font-size: var(--font-size-3xl);
  }

  .now-playing-now {
    gap: var(--space-lg);
  }
}

@media (max-width: 767px) {
  .sidebar {
    display: none;
  }

  .main-content {
    margin-left: 0;
    padding: 0 var(--space-md)
      calc(var(--tabbar-height) + var(--space-2xl) + env(safe-area-inset-bottom, 0));
  }

  /* Anti-clipping on mobile when mini-player is active */
  body:has(#mini-player:not([hidden])) .main-content,
  body.has-active-player .main-content,
  body.has-mini-player .main-content {
    padding-bottom: calc(
      var(--tabbar-height) + var(--mini-player-clearance) + var(--space-2xl) + env(safe-area-inset-bottom, 0)
    );
  }

  /* The floating bar: inset from the edges, glass, one active pill. */
  .tabbar {
    position: fixed;
    inset-inline: var(--nav-float-inset);
    bottom: calc(var(--space-xs) + env(safe-area-inset-bottom, 0));
    z-index: var(--z-sticky);
    display: flex;
    align-items: stretch;
    gap: var(--space-3xs);
    block-size: var(--tabbar-height);
    padding: var(--space-3xs);
    background: var(--glass-bg-strong);
    border: var(--hairline) solid var(--glass-border);
    border-radius: var(--radius-2xl);
    box-shadow: var(--shadow-float);
    backdrop-filter: blur(var(--glass-blur)) saturate(160%);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(160%);
  }

  /* No top rule any more: the bar is a floating surface, not a strip. */
  .tabbar::before {
    display: none;
  }

  .tabbar-link {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3xs);
    min-width: var(--tap-min);
    min-height: var(--tap-min);
    padding: var(--space-3xs);
    border-radius: var(--radius-xl);
    color: var(--color-text-muted);
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-medium);
    letter-spacing: 0.01em;
    line-height: var(--line-height-tight);
    text-decoration: none;
    transition:
      color var(--transition),
      background var(--transition);
  }

  .tabbar-link:hover {
    color: var(--color-text);
    background: var(--surface-tint);
  }

  .tabbar-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--space-xl);
    block-size: var(--space-lg);
    border-radius: var(--radius-full);
    transition: background var(--transition);
  }

  .tabbar-link.is-current,
  .tabbar-link[aria-current="page"] {
    color: var(--color-accent);
    background: var(--color-accent-dim);
  }

  .tabbar-link.is-current .tabbar-icon,
  .tabbar-link[aria-current="page"] .tabbar-icon {
    background: color-mix(in srgb, var(--color-accent) 18%, transparent);
  }

  /* Sign out and the theme control live on Settings and in the sidebar; the
     section bar keeps only the destinations, so the first card is not pushed
     below the fold. */
  .section-signout {
    display: none;
  }

  /* The page heading owns the top of the screen on a phone. */
  .page-title {
    font-size: var(--font-size-xl);
  }

  .page-header {
    align-items: flex-start;
  }

  /* The period control owns the row on a phone: five pills, one track. */
  .segmented,
  .period-selector {
    inline-size: 100%;
  }

  .segment,
  .period-btn {
    flex: 1 1 auto;
    padding-inline: var(--space-2xs);
  }
}

/* ── Ask FAB ──────────────────────────────────────────────────────────── */

/* The header's Ask button: the sheet's only opener, in a slot that owns its
   own space.  Auto-hides inside Chat and when sheets are open. */
.app-header-ask {
  flex: 0 0 auto;
  color: var(--color-accent);
  background: var(--color-accent-dim);
  border-color: color-mix(in srgb, var(--color-accent) 25%, transparent);
}

.app-header-ask:hover {
  background: var(--color-accent);
  color: var(--color-text-inverse);
}

body[data-chat-page="true"] .app-header-ask,
body[data-chat-page="true"] .ui-chat-fab,
body[data-chat-page="true"] .ask-fab,
body[data-settings-page="true"] .app-header-ask,
body[data-settings-page="true"] .ui-chat-fab,
body[data-settings-page="true"] .ask-fab,
body[data-chat-sheet="open"] .app-header-ask,
body[data-chat-sheet="open"] .ui-chat-fab,
body[data-chat-sheet="open"] .ask-fab,
body.is-chat-sheet-open .app-header-ask {
  display: none !important;
}

/* Smooth swipeable mobile bottom sheets */
@keyframes sheet-slide-up {
  from {
    transform: translate3d(0, 100%, 0);
    opacity: 0.8;
  }
  to {
    transform: translate3d(0, 0, 0);
    opacity: 1;
  }
}

.ui-chat-sheet:not([hidden]),
.player-drawer:not([hidden]) {
  animation: sheet-slide-up var(--transition-spring, 360ms cubic-bezier(0.34, 1.56, 0.64, 1)) both;
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .ui-chat-sheet:not([hidden]),
  .player-drawer:not([hidden]) {
    animation: none;
    will-change: auto;
  }
}

/* ── Forms ────────────────────────────────────────────────────────────── */

.ui-label {
  display: block;
  margin-bottom: var(--space-2xs);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.ui-input,
.ui-select,
.ui-textarea,
.search-input,
.chat-input {
  inline-size: 100%;
  min-block-size: var(--tap-comfortable);
  padding: var(--space-xs) var(--space-sm);
  background: var(--color-surface-input);
  border: var(--hairline) solid var(--glass-border);
  border-radius: var(--radius-md);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--font-size-md); /* >= 16px to prevent iOS Safari auto-zoom on focus */
  transition:
    border-color var(--transition),
    background var(--transition);
}

.ui-input:focus,
.ui-select:focus,
.ui-textarea:focus,
.search-input:focus,
.chat-input:focus {
  border-color: var(--color-accent);
  background: var(--color-surface-raised);
}

.ui-input::placeholder,
.ui-textarea::placeholder,
.search-input::placeholder,
.chat-input::placeholder {
  color: var(--color-text-muted);
}

.ui-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

/* ── Feedback: skeleton, empty, banners ───────────────────────────────── */

.skeleton,
.ui-skeleton {
  border-radius: var(--radius-md);
  background: linear-gradient(
    90deg,
    var(--skeleton-base) 25%,
    var(--skeleton-highlight) 37%,
    var(--skeleton-base) 63%
  );
  background-size: 400% 100%;
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
}

@keyframes skeleton-shimmer {
  from {
    background-position: 100% 50%;
  }
  to {
    background-position: 0 50%;
  }
}

.empty-state,
.ui-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-2xl) var(--space-lg);
  text-align: center;
  color: var(--color-text-secondary);
  background: var(--surface-tint);
  border: var(--hairline) dashed var(--glass-border);
  border-radius: var(--radius-xl);
}

.empty-state-full {
  min-height: calc(var(--space-3xl) * 3);
  justify-content: center;
}

.empty-state h2,
.ui-empty-title {
  margin: 0;
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
}

.empty-state p {
  margin: 0;
  max-width: calc(var(--space-3xl) * 7);
  font-size: var(--font-size-xs);
  line-height: var(--line-height-relaxed);
}

.ui-banner {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-lg);
  border: var(--hairline) solid var(--glass-border);
  background: var(--surface-tint);
  font-size: var(--font-size-xs);
}

.ui-banner-warning {
  background: var(--color-warning-dim);
  border-color: color-mix(in srgb, var(--color-warning) 30%, transparent);
  color: var(--color-text);
}

.ui-banner strong {
  font-size: var(--font-size-xs);
  letter-spacing: 0.02em;
}

.ui-banner p {
  margin: 0;
  color: var(--color-text-secondary);
  line-height: var(--line-height-normal);
}

/* Inline links inside a banner are controls too: give them the tap height and
   their own pill, so the 44px box reads as an action rather than as a broken
   paragraph break. */
.ui-banner a {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  min-block-size: var(--tap-min);
  margin-top: var(--space-2xs);
  padding: 0 var(--space-md);
  background: var(--surface-tint);
  border: var(--hairline) solid var(--glass-border);
  border-radius: var(--radius-full);
  color: var(--color-accent);
  font-weight: var(--font-weight-semibold);
  text-decoration: none;
}

.ui-banner a:hover {
  background: var(--surface-tint-strong);
  text-decoration: none;
}

/* ── Toast + sheet polish ─────────────────────────────────────────────── */

.toast {
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
  border: var(--hairline) solid var(--glass-border);
  background: var(--glass-bg-strong);
  backdrop-filter: blur(var(--glass-blur));
}

.modal-overlay {
  background: var(--scrim);
  backdrop-filter: blur(var(--space-2xs));
}

.modal {
  border-radius: var(--radius-2xl);
  border: var(--hairline) solid var(--glass-border);
  box-shadow: var(--shadow-float);
}

/* ── Sidebar ──────────────────────────────────────────────────────────── */

.sidebar {
  background: var(--glass-bg-strong);
  border-right: var(--hairline) solid var(--glass-border);
  backdrop-filter: blur(var(--glass-blur));
}

.sidebar-logo {
  letter-spacing: -0.02em;
}

.sidebar-link {
  border-radius: var(--radius-md);
  color: var(--color-text-secondary);
}

.sidebar-link:hover {
  background: var(--surface-tint);
  color: var(--color-text);
}

.sidebar-link.active {
  background: var(--color-accent-dim);
  color: var(--nav-active-text);
}

/* The rail is 17 destinations; four quiet labels make it scannable instead of
   one undifferentiated list. */
.sidebar-group-title {
  margin: var(--space-md) var(--space-sm) var(--space-2xs);
  font-size: var(--font-size-2xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.sidebar-nav > .sidebar-group-title:first-child {
  margin-top: var(--space-2xs);
}

/* ── Motion ───────────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .main-content > * {
    animation: none;
  }
}

/* ── Surface states (B-4 macro) ───────────────────────────────────────────
   One block for every data surface: loading, empty and error share geometry
   and differ only in weight, so a panel never collapses into bare text while
   it waits. */
.ui-surface-state {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-md);
  border-radius: var(--radius-lg);
  border: var(--hairline) solid var(--glass-border);
  background: var(--surface-tint);
  font-size: var(--font-size-xs);
  line-height: var(--line-height-normal);
}

.ui-surface-state-title {
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  letter-spacing: -0.01em;
}

.ui-surface-state-message {
  margin: 0;
  color: var(--color-text-secondary);
  max-width: 60ch;
}

.ui-surface-state--loading,
.ui-surface-state--empty {
  border-style: dashed;
}

.ui-surface-state--loading .ui-surface-state-message {
  color: var(--color-text-muted);
  animation: state-pulse 1.6s ease-in-out infinite;
}

.ui-surface-state--error {
  background: var(--color-danger-dim);
  border-color: color-mix(in srgb, var(--color-danger) 32%, transparent);
}

.ui-surface-state--error .ui-surface-state-title,
.ui-surface-state--error .ui-surface-state-message {
  color: var(--color-danger);
}

@keyframes state-pulse {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 1;
  }
}

/* ── Banner (G-7) ────────────────────────────────────────────────────────
   Headline + count on one line, the category list behind a native disclosure,
   the action as a pill. */
.ui-banner-head {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  flex-wrap: wrap;
}

.ui-banner-details {
  font-size: var(--font-size-2xs);
  color: var(--color-text-secondary);
}

.ui-banner-details > summary {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--tap-min);
  cursor: pointer;
  font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary);
  list-style: none;
}

.ui-banner-details > summary::-webkit-details-marker {
  display: none;
}

.ui-banner-details > summary::after {
  content: "▾";
  margin-inline-start: var(--space-2xs);
  transition: transform var(--transition);
}

.ui-banner-details[open] > summary::after {
  transform: rotate(180deg);
}

.ui-banner-details ul {
  margin: var(--space-2xs) 0 0;
  padding-inline-start: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  line-height: var(--line-height-relaxed);
}

/* On a phone the staleness mark is a one-line strip: the header pill already
   carries the count and the way into Settings, so the banner keeps the
   headline, the badge and the disclosure — the sentence and the second link to
   the same page are what pushed the KPI bento below the fold. */
@media (max-width: 767px) {
  .ui-banner-warning > p,
  .ui-banner-warning > a {
    display: none;
  }

  .ui-banner {
    padding: var(--space-xs) var(--space-sm);
  }
}

/* The memory feed has to start on the first screen.  Three stacked filter
   groups (type, when, trust) plus the theme pill spent most of a 390x844
   viewport before the first card; the groups still exist, they just share one
   swipeable row on a phone and the duplicated visible labels go — each group
   keeps its aria-label.  Theme switching lives in Settings on mobile, where
   the pill would otherwise be the first thing under the header. */
@media (max-width: 767px) {
  body form[data-testid="theme-toggle-form"] {
    display: none;
  }

  body .mem-filters {
    flex-direction: row;
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: var(--space-sm);
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  body .mem-filters::-webkit-scrollbar {
    display: none;
  }

  body .mem-filters .segmented-label {
    display: none;
  }

  body .mem-filters .segmented {
    flex: 0 0 auto;
  }

  body .mem-filters .mem-filter {
    flex: 0 0 auto;
  }
}

/* One strip, not two: the section nav (five pages on "You") wrapped onto a
   second row at 390px, and the staleness banner stacked its headline above its
   disclosure.  Both become single swipeable rows so the page's own content can
   start on the first screen.  ``body`` prefix: the page sheets load after
   studio.css, so an equal-specificity rule would lose. */
@media (max-width: 767px) {
  body .section-bar {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    padding-block: var(--space-2xs);
  }

  body .section-bar::-webkit-scrollbar {
    display: none;
  }

  body .section-nav {
    flex-wrap: nowrap;
    flex: 0 0 auto;
  }

  body .section-link {
    white-space: nowrap;
  }

  body .ui-banner-warning {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    padding-block: var(--space-2xs);
  }

  body .ui-banner-warning .ui-banner-head {
    flex: 0 0 auto;
    margin: 0;
    gap: var(--space-2xs);
  }

  body .ui-banner-warning .ui-banner-details {
    flex: 1 1 auto;
    margin: 0;
  }
}

/* The page header states the title twice over on a phone: "Memory & Journal",
   its subtitle, and a facts line that repeats the count the feed's own header
   already prints.  Keep the title; drop the rest, so the feed can begin on the
   first screen. */
@media (max-width: 767px) {
  body .page-header p,
  body .ui-facts {
    display: none;
  }

  body header.page-header {
    margin-block-end: var(--space-2xs);
  }
}

/* The staleness mark is a status strip, not a hero: one line at every width.
   It used to stack its headline, its sentence, its disclosure and its link into
   a ~150px card that pushed whatever the page was actually for below the fold —
   including the chat's own rail selector. */
body .ui-banner-warning {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-sm);
}

body .ui-banner-warning .ui-banner-head {
  flex: 0 0 auto;
  margin: 0;
  gap: var(--space-2xs);
}

body .ui-banner-warning > p {
  flex: 1 1 18rem;
  margin: 0;
}

body .ui-banner-warning .ui-banner-details {
  flex: 0 0 auto;
  margin: 0;
}

body .ui-banner-warning > a {
  flex: 0 0 auto;
}

/* The tiles sit one step below the hero and clearly above their 12px labels, so
   the ramp reads display → value → label instead of value and label competing. */
body .bento-grid .stat-tile-value {
  font-size: var(--font-size-xl);
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

/* The ramp needs three distinct steps on a phone as well: the hero number (35px),
   then the page title, then the tile values (--font-size-xl, 20.2px at 390px).
   With the title at the same 20.2px as the tiles the screen had no second tier. */
body h1.page-title,
body .page-header h1 {
  font-size: var(--font-size-2xl);
  line-height: 1.15;
  letter-spacing: -0.02em;
}

/* 8.8px labels fail the readable floor: the rubric asks for quiet 11–12px
   captions, not micro-print.  Uppercase tracking keeps them subordinate to the
   value without making them illegible. */
body .stat-tile-label,
body .stat-label {
  font-size: max(0.6875rem, var(--font-size-xs));
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Legibility floor.  The type scale is rem-based on a 14px root, so the two
   smallest steps resolved to 8.75px and 10.5px — badges, section tabs, the
   disclosure summary and list meta were micro-print.  ``max()`` keeps the
   ratio on a larger root and guarantees the floor here. */
:root {
  --font-size-2xs: max(11px, 0.625rem);
  --font-size-xs: max(12px, 0.75rem);
}

/* Settings is a page of form controls and the one place the Ask button has
   nothing to ask about, so the header's copy steps aside too. */
body[data-settings-page="true"] .app-header-ask {
  display: none;
}

/* An empty state must not end under the floating Ask button or the tab bar:
   the copy is the page's whole content on /library, so it gets the room. */
body .empty-state {
  padding-block-end: calc(var(--space-3xl) + env(safe-area-inset-bottom));
}

/* The sideways-scroll cue: a strip that overflows fades at its right edge until
   it is scrolled to the end, so "Settings" past the fold reads as "more to the
   right" rather than as a clipped control.  shell.js owns the two classes. */
body .section-bar.is-scrollable:not(.is-scrolled-end),
body .subnav.is-scrollable:not(.is-scrolled-end),
body .page-tabs.is-scrollable:not(.is-scrolled-end),
body .mem-filters.is-scrollable:not(.is-scrolled-end) {
  mask-image: linear-gradient(
    to right,
    #000 0,
    #000 calc(100% - 32px),
    transparent 100%
  );
}

/* The library's sort/filter menus and the track-filter menus are the densest
   control cluster in the app and were rendering 34px rows — under the 44px tap
   floor the rest of the shell keeps. */
body .library-sheet-option,
body .track-filters-option,
body .library-sheet-option a,
body .track-filters-option a {
  /* A 3-column grid lands on 43.97px; the floor is a floor.  These menus are
     pointer targets on a desktop too, so the rule is not phone-only. */
  min-block-size: 44px;
  min-inline-size: 44px;
  display: flex;
  align-items: center;
}

/* A select with ``appearance: none`` and no caret reads as a static value: the
   Settings rail pickers looked like text until you clicked them.  Give every
   select back its affordance, on both themes, with a mid-grey chevron. */
body .ui-select {
  padding-inline-end: calc(var(--space-lg) + var(--space-xs));
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%23808080' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-sm) center;
  background-size: 12px 8px;
  cursor: pointer;
}

/* Two library controls measured 42px and 18px — just under the tap floor every
   other control keeps. */
body .library-sheet-summary,
body .track-filters-summary {
  min-block-size: 44px;
  display: flex;
  align-items: center;
}

body .track-filters-clear {
  min-block-size: 44px;
  display: inline-flex;
  align-items: center;
  padding-inline: var(--space-2xs);
}

/* "pending" was the last caption under the 11px floor (10.08px measured). */
body .badge--info,
body .ui-badge--info,
body .status-badge--pending {
  font-size: max(11px, var(--font-size-2xs));
}

/* The now-playing disclosure summary was a 24px tap target inside a card of
   44px controls. */
body [data-surface="now-playing"] summary,
body .now-playing-why summary {
  min-block-size: 44px;
  display: flex;
  align-items: center;
}

/* The chat composer's textarea measured under the 44px tap floor when it held
   one line; a text field needs the same target as a button. */
body #chat-input,
body .chat-composer textarea {
  /* 46, not 44: the border box lands a hair under 44 once the composer's
     padding is folded in, and the floor is measured on the border box. */
  min-block-size: calc(var(--tap-min) + 2px);
}

/* The field floor, on the border box: 44px of `min-height` measured 43.99 once
   a field's own border was folded in, so every text field gets two pixels of
   headroom and the tap-target check is satisfied by the rendered box. */
body .ui-input,
body .ui-select,
body .ui-textarea {
  min-block-size: calc(var(--tap-min) + 2px);
}

/* A 44px min-size inside a flex row rounded to 43.99999px, under the tap floor
   as measured.  One pixel of headroom makes the rendered box honest. */
body .chat-tab,
body .period-btn,
body .chat-rail-chip,
body .tabbar-link {
  min-block-size: calc(var(--tap-min) + 1px);
}

/* A prose link inside a paragraph is still a target: 16px tall text failed the
   floor on the three cross-links in Settings.  Padding gives the hit area the
   height without changing the paragraph's look. */
body main p a,
body .settings-facts a,
body .settings-note a {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--tap-min);
  padding-inline: 2px;
}

/* The history row's relative-time badge landed at 10.08px. */
body .badge,
body .ui-badge {
  font-size: max(11px, var(--font-size-2xs));
}

/* The library header pushes its search to the right on a desktop; once it wraps
   onto its own row on a phone, that auto margin left an empty 88px column and a
   191px field.  On a phone the field owns the row. */
@media (max-width: 767px) {
  body .library-header .library-search {
    flex: 1 1 100%;
    margin-inline-start: 0;
  }

  body .library-header .library-search-input {
    flex: 1 1 auto;
    inline-size: 100%;
  }
}

/* A horizontally scrollable strip has to look scrollable: the section bar ran
   its last chip off the right edge with the scrollbar hidden, so "SETTINGS" and
   "TIME MACHINE" read as clipped rather than as one swipe away. */
@media (max-width: 767px) {
  body .section-bar {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
  }
}

/* Every disclosure row in the page body is a primary control, not a caption. */
body main summary {
  min-block-size: var(--tap-min);
  display: flex;
  align-items: center;
}

/* The last sub-11px holdouts on /insights: the profile ratio labels and the
   listening-journal legend, both 0.72rem against a 14px root (10.08px). */
body .profile-stat-label,
body .ij-legend li,
body .ij-legend span {
  font-size: max(11px, var(--font-size-2xs));
}

/* Two last tap floors: the chat rail's model summary (a 24px disclosure that is
   the only way into the model picker) and the desktop sidebar links (40px). */
body .ui-chat-model-summary {
  min-block-size: var(--tap-min);
  display: flex;
  align-items: center;
}

body .sidebar-link {
  min-block-size: var(--tap-min);
}

/* The stale-ingest banner is one row at every width.  Its sentence stacked under
   the head on a desktop (74px) while the phone folded it away (54px) — same
   component, two shapes, and the tall one competed with the page title for the
   first glance.  One row: head, sentence (ellipsised when narrow), disclosure. */
body .ui-banner-warning {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--space-sm);
}

body .ui-banner-warning > p {
  margin: 0;
  flex: 1 1 auto;
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body .ui-banner-warning .ui-banner-head,
body .ui-banner-warning .ui-banner-details {
  flex: 0 0 auto;
}

/* An anchor styled as a primary button keeps the button's ink even when it sits
   inside a row whose own link rule (`color: inherit`, same specificity) would
   otherwise repaint it — "Open on Spotify" measured 3.2:1 from inherited text. */
body a.ui-btn-primary,
body a.btn-primary,
body button.ui-btn-primary {
  color: var(--color-on-accent);
}
