/* ============================================================================
   Saltado Sessions - Vault Drive (web)

   One stylesheet, no build step, no CDN. The palette is lifted from the app's
   Default theme (client/src/styles.css) and the site's shared layer
   (website/site.css) so this reads as the same product rather than a third
   design: Space Grotesk throughout, the muted amber accent, flat surfaces,
   hairline borders, quiet motion.

   Two deliberate departures from the app, both because this runs in a browser
   next to Drive and Dropbox rather than inside an Ableton-shaped window:

   1. Light mode is a warm off-white, not the app's classic-light mid gray.
      The app's light theme mimics Live's own light skin; a #a2a2a2 canvas on
      the open web reads as a broken page, not as a choice.
   2. Radii sit between the site's 2px and the app's 12px (6px controls,
      10px cards). Small enough to stay precise, round enough to stop looking
      like a 2014 admin panel beside Drive.

   THEMING: index.html resolves auto/dark/light before first paint and writes
   the RESULT to <html data-theme>. So there is exactly one light block below,
   not two identical ones (a media query for auto plus an attribute rule for
   the manual override). Never add a `prefers-color-scheme` block for tokens
   here; it would silently fight the attribute.

   ⚠️ TYPE: there are SIX sizes and no others. Every `font-size` in this file
   is one of the six --fs-* tokens, and a literal px font-size anywhere below
   is a bug. The first version of this file declared six tokens and then set
   nine literal sizes alongside them, seven of which sat inside a 4.5px band
   (13 vs 13.5, 11.5 vs 12), which is not a scale, it is noise. If a new role
   seems to need a seventh size, it needs an existing one.
   ========================================================================= */

@font-face {
  font-family: 'Space Grotesk';
  src: url('fonts/SpaceGrotesk-latin-var.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------- tokens */

:root {
  --font: 'Space Grotesk', 'Helvetica Neue', 'Arial Nova', Arial, 'Segoe UI', system-ui, sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  /* THE SCALE. Six values, each with one job.
       11  micro    uppercase tracked labels: column heads, section labels, tags
       12  meta     second lines, hints, clocks, anything explaining something else
       14  body     the default: row names, cells, buttons, inputs, prose
       16  lg       section headings: dialog titles, empty-state titles, wordmark
       20  title    page titles
       24  hero     the sign-in headline, and the no-JS message
     Steps are 1.09 / 1.17 / 1.14 / 1.25 / 1.20, tight at the bottom where the
     roles differ by weight and colour too, wider at the top where size is
     doing the work on its own. */
  --fs-micro: 11px;
  --fs-meta: 12px;
  --fs-body: 14px;
  --fs-lg: 16px;
  --fs-title: 20px;
  --fs-hero: 24px;

  /* dark is the default, matching the app */
  color-scheme: dark;
  --bg: #2b2b2b;
  --surface: #363636;
  --surface-2: #424242;
  --sidebar: #232323;
  --border: #4d4d4d;
  --border-soft: #3d3d3d;
  --text: #cfcfcf;
  --text-strong: #ececec;
  --muted: #a6a6a6;          /* 5.8:1 on --bg, 5.3:1 on a hovered row */
  /* ⚠️ Tuned against a HOVERED row, not the resting canvas. --hover lifts the
     composite by about 7 units, and the previous #9a9a9a measured 5.0:1 at
     rest but 4.42:1 the moment a row was hovered, which is a fail in the state
     a row spends its whole life in while being read. */
  --faint: #9e9e9e;          /* 5.2:1 on --bg, 4.8:1 hovered, 5.8:1 on --sidebar */
  --icon: #b0b0b0;           /* 6.5:1 on --bg */
  --accent: #d99146;          /* FILLS, borders, icons. Never text. */
  --accent-hover: #e6a25c;
  --accent-ink: #1a1206;      /* text that sits ON the accent */
  /* ⚠️ Accent as INK gets its own token, cleared against the WORST composited
     point on the screen: the sign-in footer sits over the flourish's ember,
     which lifts the backdrop to about rgb(69,60,50), where the raw hue
     measured 4.15:1. This reads 5.21:1 there and 6.79:1 on plain --bg. Same
     value the account dashboard uses, so the two surfaces stay one family.
     Never put var(--accent) on text. */
  --accent-text: #e8a663;
  --accent-soft: rgba(217, 145, 70, 0.18);
  --accent-line: rgba(217, 145, 70, 0.34);
  /* The decorative wash behind the sign-in and empty screens. Weaker than
     --accent-soft on purpose: this one composites UNDER body text, so its
     strength is a legibility budget, not a taste call. */
  --flourish-wash: rgba(217, 145, 70, 0.10);
  --ok: #67b84b;
  --warn: #e2b13a;
  --error: #e5544b;
  --warn-ink: #eec25c;
  --error-ink: #f0837b;
  --ok-ink: #8ad06f;

  /* 0.035 not 0.045: the row-hover overlay is the thing that composites every
     secondary line lighter, so its weight is a contrast decision as much as a
     visual one. Still clearly visible against #2b2b2b. */
  --hover: rgba(255, 255, 255, 0.035);
  --hover-2: rgba(255, 255, 255, 0.075);
  --well: rgba(0, 0, 0, 0.22);
  --scrim: rgba(0, 0, 0, 0.6);

  --radius-sm: 4px;
  --radius: 6px;
  --radius-lg: 10px;
  --sh-1: 0 1px 2px rgba(0, 0, 0, 0.3);
  --sh-2: 0 10px 30px rgba(0, 0, 0, 0.45), 0 2px 6px rgba(0, 0, 0, 0.3);
  --sh-3: 0 24px 70px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.4);

  --sidebar-w: 258px;
  --header-h: 60px;
  --player-h: 72px;
  --row-h: 48px;
}

:root[data-theme='light'] {
  color-scheme: light;
  --bg: #f3f2ef;
  --surface: #ffffff;
  --surface-2: #f7f6f3;
  --sidebar: #eae8e3;
  --border: #d8d4cc;
  --border-soft: #e4e0d8;
  --text: #23211e;
  --text-strong: #100f0d;
  --muted: #5f5b55;          /* 6.0:1 on --bg */
  /* ⚠️ Measured on --sidebar (#eae8e3), NOT on --bg. The sidebar is the darker
     of the two surfaces this sits on, so it is the one that decides: a --faint
     tuned against --bg alone came out at 4.33:1 for the machine list and the
     player's second line. */
  --faint: #63605a;          /* 5.1:1 on --sidebar, 5.5:1 on --bg */
  --icon: #57534c;           /* 7.0:1 on --bg */
  /* ⚠️ The light accent is darker than the dark theme's on purpose, because it
     is also INK here: the playing row's name, links in hints, the support
     address. The prettier #a5631a measured 4.26:1 as text on --bg, which is a
     fail at body size; this reads 5.3:1 as text and 6.0:1 white-on-fill. */
  --accent: #8f5514;
  --accent-hover: #7a4710;
  --accent-ink: #ffffff;
  /* In light mode the flourish DARKENS its backdrop (it mixes toward the
     accent and vignettes), so ink contrast only ever improves there and the
     fill colour already clears the worst point at 5.39:1. Same value as the
     account dashboard. */
  --accent-text: #8f5514;
  --accent-soft: rgba(143, 85, 20, 0.13);
  --accent-line: rgba(143, 85, 20, 0.4);
  /* Weaker again than the dark one: in light mode this wash DARKENS the
     canvas, so it eats contrast for the dark ink above it. Matched to the
     softened shader so the WebGL and no-WebGL paths land in the same place. */
  --flourish-wash: rgba(143, 85, 20, 0.07);
  --ok: #3f8f2f;
  --warn: #a67c10;
  --error: #c0392b;
  --warn-ink: #6d5008;
  --error-ink: #8c2418;
  --ok-ink: #2b6520;

  --hover: rgba(0, 0, 0, 0.035);
  --hover-2: rgba(0, 0, 0, 0.06);
  --well: rgba(0, 0, 0, 0.05);
  --scrim: rgba(28, 26, 23, 0.42);

  --sh-1: 0 1px 2px rgba(35, 31, 26, 0.07);
  --sh-2: 0 10px 28px rgba(35, 31, 26, 0.13), 0 1px 3px rgba(35, 31, 26, 0.08);
  --sh-3: 0 24px 60px rgba(35, 31, 26, 0.18), 0 2px 6px rgba(35, 31, 26, 0.1);
}

/* ------------------------------------------------------------------ base */

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: 1.5;
  letter-spacing: 0.002em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;               /* the shell owns its own scroll regions */
}

h1, h2, h3, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
button, input, select, textarea { font: inherit; color: inherit; }
/* Options are rendered by the OS but still report a computed size; pinning it
   keeps the scale honest when the whole page is enumerated. */
option { font-size: var(--fs-body); }
a { color: inherit; text-decoration: none; }

/* ⚠️ EVERY icon is an <svg><use href="#i-..."> with no intrinsic size, so an
   svg that no rule happens to size falls back to the CSS replaced-element
   default of 300x150 and paints a glyph the size of a paragraph across the
   layout. That shipped once, in the audio rows. icon() in app.js also writes
   width/height attributes as a floor; this is the second belt. */
svg { display: block; width: 16px; height: 16px; flex: none; }

/* ⚠️ The UA sheet's `[hidden] { display: none }` loses to ANY display
   declaration in this file, so `.player-error { display: flex }` kept a hidden
   node in the flex layout and it ate 377px out of the middle of the player bar.
   Global, and !important, because the next author to write `display:` on a
   toggled element should not have to rediscover this. */
[hidden] { display: none !important; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }

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

/* The shell and the sign-in screen both size themselves against #root, and
   both sit above the flourish canvas. */
#root { position: relative; z-index: 1; height: 100%; }

.skip-link {
  position: fixed; top: 8px; left: 8px; z-index: 200;
  padding: 9px 14px; border-radius: var(--radius);
  background: var(--accent); color: var(--accent-ink); font-weight: 600;
  transform: translateY(-160%);
  transition: transform 0.14s ease;
}
.skip-link:focus { transform: none; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* thin, quiet scrollbars: the default Windows bar is loud next to this palette */
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border); border-radius: 8px;
  border: 3px solid transparent; background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: padding-box; }

/* ------------------------------------------------------- first paint / no JS */

.boot {
  position: fixed; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
}
.boot-mark {
  width: 46px; height: 41px;
  background: var(--muted);
  -webkit-mask: url('favicon.svg') center / contain no-repeat;
  mask: url('favicon.svg') center / contain no-repeat;
  opacity: 0.5;
}
.boot-text { color: var(--faint); font-size: var(--fs-meta); }

.noscript {
  position: fixed; inset: 0; z-index: 300;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; padding: 32px; text-align: center; background: var(--bg);
}
.noscript h1 { font-size: var(--fs-hero); font-weight: 700; letter-spacing: -0.02em; }
.noscript p { max-width: 46ch; color: var(--muted); }

/* ------------------------------------------------------------- flourish */

/* Behind everything, only visible where the layout is deliberately open
   (sign-in, and the two big empty states). It is decoration: it never
   receives a pointer event and it never blocks first paint. */
#flourish {
  position: fixed; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.9s ease;
}
#flourish.on { opacity: 1; }
body[data-view='drive'] #flourish, body[data-view='shares'] #flourish { display: none; }
body[data-view='drive'][data-hero='1'] #flourish { display: block; }

/* Static fallback when WebGL is unavailable or motion is reduced: the same
   composition as the shader's resting frame, done in gradients. */
.flourish-fallback {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(120% 80% at 50% 108%, var(--flourish-wash) 0%, transparent 62%),
    radial-gradient(90% 60% at 18% -10%, var(--hover-2) 0%, transparent 70%);
}

/* --------------------------------------------------------------- buttons */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 34px; padding: 0 14px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--fs-body); font-weight: 600; letter-spacing: 0.005em;
  cursor: pointer; white-space: nowrap;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
}
.btn:hover:not(:disabled):not([aria-disabled='true']) { background: var(--surface-2); border-color: var(--muted); }
.btn:active:not(:disabled) { background: var(--hover-2); }
/* Native :disabled is genuinely inert, unfocusable and exempt from contrast
   minimums, so alpha is honest there. aria-disabled is NOT: that control is
   still focusable and still answers with a reason when pressed, so it has to
   stay readable. Dimmer ink, never lower alpha. */
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn[aria-disabled='true'] {
  opacity: 1; cursor: not-allowed;
  color: var(--muted); background: var(--surface); border-color: var(--border-soft);
}
.btn svg { width: 16px; height: 16px; }

.btn.primary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.btn.primary:hover:not(:disabled):not([aria-disabled='true']) {
  background: var(--accent-hover); border-color: var(--accent-hover);
  box-shadow: 0 4px 14px var(--accent-soft);
}
.btn.ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn.ghost:hover:not(:disabled) { background: var(--hover); border-color: transparent; color: var(--text); }
.btn.danger { color: var(--error-ink); }
.btn.danger:hover:not(:disabled) { border-color: var(--error); background: var(--hover); }
/* The confirm button of a destructive dialog. Declared after .btn.primary so
   it wins the tie, or it would paint the amber fill with red ink on it. */
.btn.primary.danger {
  background: var(--error); border-color: var(--error); color: #fff;
}
.btn.primary.danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--error) 86%, #000);
  border-color: color-mix(in srgb, var(--error) 86%, #000);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--error) 30%, transparent);
}
.btn.primary.danger[data-busy='1']::after { color: #fff; }
.btn.block { width: 100%; }
.btn.lg { height: 42px; padding: 0 18px; }

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none;
  background: transparent; border: 1px solid transparent; border-radius: var(--radius);
  color: var(--icon); cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.icon-btn:hover:not(:disabled):not([aria-disabled='true']) { background: var(--hover-2); color: var(--text-strong); }
.icon-btn:disabled { opacity: 0.42; cursor: not-allowed; }
/* Focusable, announced, and answers with a reason: it must stay legible. */
.icon-btn[aria-disabled='true'] { opacity: 1; cursor: not-allowed; color: var(--faint); }
.icon-btn svg { width: 17px; height: 17px; }
.icon-btn.sm { width: 28px; height: 28px; }
.icon-btn.sm svg { width: 16px; height: 16px; }

/* a button whose whole job is to spin while it works */
.btn[data-busy='1'] { position: relative; color: transparent; }
.btn[data-busy='1'] svg { visibility: hidden; }
.btn[data-busy='1']::after {
  content: ''; position: absolute; width: 15px; height: 15px;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
  color: var(--text);
  animation: spin 0.7s linear infinite;
}
.btn.primary[data-busy='1']::after { color: var(--accent-ink); }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------- inputs */

.field { display: flex; flex-direction: column; gap: 7px; }
.field > label {
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--muted);
}
.input, select.input {
  width: 100%; height: 42px; padding: 0 13px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  transition: border-color 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
}
.input::placeholder { color: var(--faint); }
.input:hover:not(:focus) { border-color: var(--muted); }
.input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.input[aria-invalid='true'] { border-color: var(--error); }
.input[aria-invalid='true']:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 30%, transparent); }
/* No extra tracking: the 36-character key placeholder at body size already
   measures 302px inside a 328px field, and 0.055em of letter-spacing pushed
   it to 330px, i.e. clipped. Monospace separates the characters anyway. */
.input.key { font-family: var(--mono); }
select.input {
  appearance: none; padding-right: 34px; cursor: pointer;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 17px) 19px, calc(100% - 12px) 19px;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.hint { font-size: var(--fs-meta); color: var(--muted); line-height: 1.5; }
.hint a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }

/* --------------------------------------------------------------- notices */

.notice {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 11px 13px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface);
  line-height: 1.45;
}
.notice svg { width: 17px; height: 17px; margin-top: 2px; }
.notice.error { border-color: var(--error); background: color-mix(in srgb, var(--error) 9%, var(--surface)); color: var(--error-ink); }
.notice.warn { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 9%, var(--surface)); color: var(--warn-ink); }
.notice strong { font-weight: 700; }

/* ============================================================== sign in */

.signin {
  position: relative; z-index: 1;
  height: 100%; overflow-y: auto;
  display: grid; grid-template-rows: 1fr auto;
  padding: 24px;
}
.signin-inner {
  align-self: center; justify-self: center;
  width: 100%; max-width: 400px;
  padding: 24px 0;
}
.lockup { display: flex; align-items: center; gap: 11px; margin-bottom: 30px; }
.lockup .mark { width: 40px; height: 36px; color: var(--accent); }
.lockup .words { display: flex; flex-direction: column; line-height: 1; }
.lockup .kicker {
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.3em; color: var(--muted);
  margin-bottom: 3px;
}
.lockup .product { font-size: var(--fs-title); font-weight: 700; letter-spacing: 0.06em; color: var(--text-strong); }

.signin h1 {
  font-size: var(--fs-hero); font-weight: 700; letter-spacing: -0.022em; line-height: 1.14;
  color: var(--text-strong); margin-bottom: 10px;
}
.signin .lede { color: var(--muted); font-size: var(--fs-lg); line-height: 1.5; margin-bottom: 28px; max-width: 34ch; }

.signin-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--sh-2);
  padding: 22px;
  display: flex; flex-direction: column; gap: 15px;
}
.signin-foot {
  justify-self: center;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap; justify-content: center;
  padding-top: 20px;
  /* ⚠️ One step stronger than the equivalent text elsewhere, because this row
     sits at the bottom of the screen, which is exactly where the flourish's
     ember peaks. --faint measured 4.03:1 here and --muted 4.43:1 against the
     lifted backdrop. Position is part of a contrast budget when there is a
     backdrop behind the content. */
  font-size: var(--fs-meta); color: var(--muted);
}
.signin-foot a { color: var(--text); }
.signin-foot a:hover { color: var(--accent-text); }
.signin-foot .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: 0.6; }

/* ============================================================= app shell */

.shell {
  position: relative; z-index: 1;
  height: 100%;
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  grid-template-rows: 1fr;
}
body.has-player .shell { height: calc(100% - var(--player-h)); }

/* ------------------------------------------------------------- sidebar */

.sidebar {
  grid-column: 1;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--sidebar);
  border-right: 1px solid var(--border-soft);
}
.sidebar-head {
  display: flex; align-items: center; gap: 10px;
  height: var(--header-h); padding: 0 16px; flex: none;
  border-bottom: 1px solid var(--border-soft);
}
.sidebar-head .mark { width: 26px; height: 24px; color: var(--accent); }
.sidebar-head .words { display: flex; flex-direction: column; line-height: 1; min-width: 0; }
.sidebar-head .kicker {
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.26em; color: var(--faint);
  margin-bottom: 3px; transform: scale(0.78); transform-origin: left center;
}
.sidebar-head .product { font-size: var(--fs-lg); font-weight: 700; letter-spacing: 0.05em; color: var(--text-strong); }

.sidebar-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 16px 10px 10px; }
.side-label {
  display: block; padding: 0 8px 8px;
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--faint);
}
.side-group + .side-group { margin-top: 22px; }

.machine {
  display: grid; grid-template-columns: 18px 1fr; gap: 10px; align-items: start;
  width: 100%; text-align: left;
  padding: 9px; border-radius: var(--radius);
  border: 1px solid transparent; background: transparent; cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.machine:hover { background: var(--hover); }
.machine[aria-current='true'] { background: var(--hover-2); border-color: var(--border-soft); }
.machine[aria-current='true'] .machine-name { color: var(--text-strong); }
/* ⚠️ Measured on the COMPOSITED background, not on --sidebar. The selection
   overlay lifts #232323 to about #333333, which dropped --faint here to
   4.46:1; --muted rides the same lift to 5.2:1 dark and 4.7:1 light. A
   contrast check against the base colour of a surface that has something
   painted over it is not a contrast check. */
.machine[aria-current='true'] .machine-meta { color: var(--muted); }
.machine svg { width: 18px; height: 18px; color: var(--icon); margin-top: 1px; }
.machine[aria-current='true'] svg { color: var(--accent); }
.machine-body { min-width: 0; }
.machine-name {
  display: block; font-size: var(--fs-body); font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.machine-meta {
  display: block; font-size: var(--fs-meta); color: var(--faint); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.side-link {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px; border-radius: var(--radius);
  border: 1px solid transparent; background: transparent; cursor: pointer;
  font-size: var(--fs-body); font-weight: 600; color: var(--text); text-align: left;
  transition: background 0.12s ease;
}
.side-link:hover { background: var(--hover); }
.side-link[aria-current='page'] { background: var(--hover-2); border-color: var(--border-soft); }
.side-link[aria-current='page'] svg { color: var(--accent); }
.side-link svg { width: 18px; height: 18px; color: var(--icon); }

.sidebar-foot { flex: none; padding: 14px 16px 16px; border-top: 1px solid var(--border-soft); }

.meter-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font-size: var(--fs-meta); color: var(--muted); margin-bottom: 7px;
}
.meter-head b { color: var(--text); font-weight: 600; }
.meter { height: 5px; border-radius: 3px; background: var(--well); overflow: hidden; }
.meter i {
  display: block; height: 100%; border-radius: 3px; background: var(--accent);
  transition: width 0.5s ease;
}
.meter.warn i { background: var(--warn); }
.meter.crit i { background: var(--error); }
.meter-note { margin-top: 7px; font-size: var(--fs-meta); line-height: 1.4; color: var(--warn-ink); }
.meter.crit + .meter-note { color: var(--error-ink); }

/* appearance override, sitting with the other account-level controls */
.side-appearance { margin-top: 14px; }
.side-appearance > span {
  display: block; margin-bottom: 6px;
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--faint);
}
.theme-seg {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px;
  padding: 2px; border-radius: var(--radius);
  background: var(--well); border: 1px solid var(--border-soft);
}
.theme-seg button {
  height: 26px; padding: 0 4px;
  background: none; border: none; border-radius: var(--radius-sm);
  color: var(--muted); font-size: var(--fs-meta); font-weight: 600; cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.theme-seg button:hover { color: var(--text-strong); }
.theme-seg button[aria-pressed='true'] {
  background: var(--surface); color: var(--text-strong); box-shadow: var(--sh-1);
}
.sidebar-foot > .btn { margin-top: 14px; }

/* -------------------------------------------------------------- content */

.main { grid-column: 2; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.main:focus { outline: none; }

.topbar {
  flex: none; min-height: var(--header-h);
  display: flex; align-items: center; gap: 12px;
  padding: 0 20px;
  border-bottom: 1px solid var(--border-soft);
  background: var(--bg);
}
.nav-toggle { display: none; }

.crumbs {
  display: flex; align-items: center; gap: 1px;
  min-width: 0; flex: 1 1 auto; overflow: hidden;
}
.crumbs .crumb {
  display: inline-flex; align-items: center; max-width: 26ch;
  padding: 5px 8px; border-radius: var(--radius-sm);
  background: none; border: none; cursor: pointer;
  font-size: var(--fs-body); font-weight: 600; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: color 0.12s ease, background 0.12s ease;
}
.crumbs .crumb:hover { color: var(--text-strong); background: var(--hover); }
.crumbs .crumb.here { color: var(--text-strong); cursor: default; }
.crumbs .crumb.here:hover { background: none; }
.crumbs .sep { width: 14px; height: 14px; color: var(--faint); opacity: 0.8; }
.crumbs .ell { color: var(--faint); padding: 0 3px; }

.topbar-actions { display: flex; align-items: center; gap: 8px; flex: none; }

/* ---- search ---- */

.search { position: relative; flex: 0 1 300px; min-width: 150px; }
.search > svg {
  position: absolute; left: 11px; top: 50%; margin-top: -8px;
  width: 16px; height: 16px; color: var(--icon); pointer-events: none;
}
.search .input {
  height: 34px; padding: 0 34px;
  background: var(--surface-2); border-color: transparent;
}
.search .input:hover:not(:focus) { border-color: var(--border); }
.search .input:focus { background: var(--surface); }
.search-clear { position: absolute; right: 3px; top: 3px; width: 28px; height: 28px; }

.results-note {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 0 12px 12px;
  font-size: var(--fs-meta); color: var(--muted);
}
.results-note b { color: var(--text); font-weight: 600; }
.results-note .aside { color: var(--faint); }

/* ---- page head ---- */

.page-head { margin: 0 0 16px; padding: 0 12px; }
.page-head h1 {
  font-size: var(--fs-title); font-weight: 700; letter-spacing: -0.018em; line-height: 1.2;
  color: var(--text-strong);
  overflow-wrap: anywhere;
}
.page-head .page-meta {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 5px; color: var(--muted);
}
.page-head .page-meta .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: 0.6; }
.page-head > p { color: var(--muted); margin-top: 6px; max-width: 66ch; }

.page-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; }
.page-pad { padding: 20px 20px 40px; flex: 0 0 auto; }
/* Empty and error panels get the whole area and sit optically centred in it,
   rather than parked a quarter of the way down a 700px void. */
.page-pad.center {
  flex: 1 1 auto;
  display: flex; align-items: center; justify-content: center;
  padding-bottom: 8vh;
}

.ro-banner, .quota-banner {
  display: flex; align-items: flex-start; gap: 11px;
  margin: 16px 20px 0;
  padding: 11px 14px;
  border: 1px solid var(--warn); border-radius: var(--radius);
  background: color-mix(in srgb, var(--warn) 8%, var(--bg));
  color: var(--warn-ink); line-height: 1.45;
}
.ro-banner svg, .quota-banner svg { width: 17px; height: 17px; margin-top: 2px; }
.quota-banner.crit {
  border-color: var(--error);
  background: color-mix(in srgb, var(--error) 9%, var(--bg));
  color: var(--error-ink);
}
.quota-banner b { font-weight: 700; }

/* ---------------------------------------------------------- file table */

.table { width: 100%; }

.thead {
  display: grid;
  grid-template-columns: 1fr 112px 152px 100px;
  align-items: center;
  gap: 12px;
  padding: 0 12px 6px;
  border-bottom: 1px solid var(--border-soft);
}
/* ONE header convention for both tables. The file table's columns are sort
   buttons and the shares table's are plain labels, but they are the same rank
   of thing on screen and were reading as two different design systems. */
.th, .sort-btn {
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--faint);
}
.sort-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 6px; margin-left: -6px;
  background: none; border: none; border-radius: var(--radius-sm);
  cursor: pointer;
  transition: color 0.12s ease, background 0.12s ease;
}
.sort-btn:hover { color: var(--text-strong); background: var(--hover); }
.sort-btn[aria-sort='ascending'], .sort-btn[aria-sort='descending'] { color: var(--text-strong); }
.sort-btn .sort-arrow {
  width: 12px; height: 12px; opacity: 0;
  transition: opacity 0.12s ease, transform 0.12s ease;
}
.sort-btn:hover .sort-arrow { opacity: 0.45; }
.sort-btn[aria-sort='ascending'] .sort-arrow { opacity: 1; transform: rotate(180deg); }
.sort-btn[aria-sort='descending'] .sort-arrow { opacity: 1; }
.thead .num { justify-content: flex-end; margin-left: 0; margin-right: -6px; }
.thead .col-right { display: flex; justify-content: flex-end; }

.row {
  display: grid;
  grid-template-columns: 1fr 112px 152px 100px;
  align-items: center;
  gap: 12px;
  min-height: var(--row-h);
  padding: 4px 12px;
  border-bottom: 1px solid var(--border-soft);
  position: relative;
}
.row:last-child { border-bottom: none; }
.row:hover { background: var(--hover); }
.row.locked { color: var(--faint); }

/* The name is the click target for the whole row's primary action. It is a
   real button so keyboard and screen readers get it without ARIA theatre;
   the ::after stretches its hit area across the row, and the action buttons
   sit above it on z-index so they stay clickable. */
.row-main {
  display: flex; align-items: center; gap: 11px;
  min-width: 0;
  background: none; border: none; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.row-main::after { content: ''; position: absolute; inset: 0; }
.row.locked .row-main { cursor: default; }
.row.locked .row-main::after { content: none; }
.row-main:focus-visible { outline: none; }
.row:has(.row-main:focus-visible) {
  outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--radius-sm);
}

/* ⚠️ Fixed box plus overflow:hidden. The audio rows stack two glyphs in here
   and an unsized <svg> would paint at 300x150 straight across the filename. */
.row-icon {
  position: relative; overflow: hidden;
  width: 18px; height: 18px; flex: none;
  color: var(--icon);
}
.row-icon svg { width: 18px; height: 18px; display: block; }
.row-icon svg.base, .row-icon svg.swap { position: absolute; top: 0; left: 0; }
.row.is-dir .row-icon { color: var(--accent); }
.row.playing .row-icon { color: var(--accent); }
/* audio rows swap their glyph for a play triangle on hover, so "click plays"
   is telegraphed instead of guessed */
.row-icon svg.swap { opacity: 0; transition: opacity 0.12s ease; }
.row-icon svg.base { transition: opacity 0.12s ease; }
.row.is-audio:hover .row-icon svg.base { opacity: 0; }
.row.is-audio:hover .row-icon svg.swap { opacity: 1; color: var(--accent); }

.row-text { min-width: 0; display: flex; flex-direction: column; }
.row-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-body); font-weight: 500; color: var(--text);
}
.row.is-dir .row-name { font-weight: 600; }
.row.playing .row-name { color: var(--accent-text); }
/* The informative sub-line (a root's disk path, a locked root's totals, a
   search hit's folder). Shown at EVERY width: it is the reason the row is
   worth more than its name. */
.row-sub {
  font-size: var(--fs-meta); color: var(--faint); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The redundant one: size and date, which the columns already carry on a wide
   screen and only earn their place once those columns are gone. */
.row-sub-compact { display: none; font-size: var(--fs-meta); color: var(--faint); margin-top: 1px; }

.cell { color: var(--muted); font-variant-numeric: tabular-nums; }
.cell.num { text-align: right; }
.cell.dim { color: var(--faint); }

.row-actions {
  display: flex; align-items: center; justify-content: flex-end; gap: 2px;
  position: relative; z-index: 1;
  opacity: 0;
  transition: opacity 0.1s ease;
}
.row:hover .row-actions,
.row:focus-within .row-actions { opacity: 1; }
@media (hover: none) { .row-actions { opacity: 1; } }

.locked-note {
  grid-column: 1 / -1;
  padding: 2px 0 10px 29px;
  font-size: var(--fs-meta); color: var(--faint); max-width: 64ch;
}

/* ------------------------------------------------------- empty / states */

.empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 10px;
  padding: 24px;
  max-width: 46ch;
}
.empty .glyph { width: 44px; height: 44px; color: var(--faint); opacity: 0.55; margin-bottom: 4px; }
.empty h2 { font-size: var(--fs-lg); font-weight: 700; color: var(--text-strong); letter-spacing: -0.01em; }
.empty p { color: var(--muted); line-height: 1.55; }
.empty .btn { margin-top: 12px; }

/* A failure is not an absence. Amber frame, amber glyph, the retry as the
   loudest thing in the panel, and the code plus a way to reach a human. */
.empty.failure {
  max-width: 52ch;
  padding: 30px 32px 32px;
  border: 1px solid var(--warn); border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--warn) 7%, var(--surface));
  box-shadow: var(--sh-1);
}
.empty.failure .glyph { color: var(--warn); opacity: 1; }
.empty.failure h2 { color: var(--text-strong); }
.empty.failure p { color: var(--text); }
.fail-foot {
  display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap;
  margin-top: 14px; font-size: var(--fs-meta); color: var(--muted);
}
.fail-code {
  font-family: var(--mono); font-size: var(--fs-meta);
  padding: 2px 7px; border-radius: var(--radius-sm);
  background: var(--well); border: 1px solid var(--border-soft); color: var(--muted);
}
.fail-foot a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }

.skeleton-row {
  display: grid; grid-template-columns: 1fr 112px 152px 100px; gap: 12px; align-items: center;
  min-height: var(--row-h); padding: 0 12px;
  border-bottom: 1px solid var(--border-soft);
}
.sk {
  height: 10px; border-radius: 3px;
  background: linear-gradient(90deg, var(--hover) 25%, var(--hover-2) 37%, var(--hover) 63%);
  background-size: 300% 100%;
  animation: sheen 1.4s ease-in-out infinite;
}
.sk.icon { width: 18px; height: 18px; border-radius: var(--radius-sm); flex: none; }
@keyframes sheen { 0% { background-position: 130% 0; } 100% { background-position: -30% 0; } }
.skeleton-row .first { display: flex; align-items: center; gap: 11px; }

/* ------------------------------------------------------------ share page */

.share-row {
  display: grid; grid-template-columns: 1fr 132px 132px 96px; gap: 12px; align-items: center;
  min-height: 58px; padding: 8px 12px;
  border-bottom: 1px solid var(--border-soft);
}
.share-row:hover { background: var(--hover); }
/* ⚠️ An expired row is dimmed with INK, never with opacity on the subtree.
   `opacity: 0.6` here composited the text toward the background and dropped
   the filename to 4.13:1 and the sub-line to 2.50:1: a row you are meant to
   read (to decide whether to revoke it) rendered below AA. Dimming a subtree
   with alpha always does this, because the measured colour is no longer the
   declared one. The step down is a weaker ink plus a lighter weight, and the
   Expired/Revoked tag carries the actual state. */
.share-row.expired .share-path b { color: var(--muted); font-weight: 500; }
.share-row.expired .share-path span { color: var(--faint); }
.share-row.expired .row-icon { color: var(--faint); }
.share-main { display: flex; align-items: center; gap: 11px; min-width: 0; }
.share-path { min-width: 0; }
.share-path b {
  display: block; font-size: var(--fs-body); font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.share-path span {
  display: block; font-size: var(--fs-meta); color: var(--faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.tag {
  display: inline-flex; align-items: center; gap: 5px;
  height: 21px; padding: 0 7px; border-radius: 3px;
  border: 1px solid var(--border); background: var(--surface);
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted);
}
.tag.warn { border-color: var(--warn); color: var(--warn-ink); }

/* ---------------------------------------------------------------- modal */

.scrim {
  position: fixed; inset: 0; z-index: 90;
  background: var(--scrim);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  animation: fade 0.14s ease;
}
@keyframes fade { from { opacity: 0; } }
.modal {
  width: 100%; max-width: 470px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--sh-3);
  animation: rise 0.16s ease;
  max-height: calc(100vh - 48px); overflow-y: auto;
}
@keyframes rise { from { opacity: 0; transform: translateY(8px) scale(0.985); } }
.modal-head { display: flex; align-items: center; gap: 12px; padding: 16px 16px 12px; }
.modal-head h2 { font-size: var(--fs-lg); font-weight: 700; color: var(--text-strong); flex: 1 1 auto; }
.modal-body { padding: 0 16px 16px; display: flex; flex-direction: column; gap: 14px; }
.modal-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  padding: 13px 16px; border-top: 1px solid var(--border-soft);
  background: var(--surface-2); border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.modal-foot .spacer { flex: 1 1 auto; }

.subject {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: var(--radius);
  background: var(--well); border: 1px solid var(--border-soft);
  min-width: 0;
}
.subject svg { width: 18px; height: 18px; color: var(--accent); }
.subject div { min-width: 0; }
.subject b { display: block; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.subject span { display: block; font-size: var(--fs-meta); color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.copybox { display: flex; gap: 8px; }
.copybox .input { font-family: var(--mono); font-size: var(--fs-meta); }

/* --------------------------------------------------------------- toasts */

.toasts {
  position: fixed; left: 20px; bottom: 20px; z-index: 120;
  display: flex; flex-direction: column-reverse; gap: 8px;
  pointer-events: none;
}
body.has-player .toasts { bottom: calc(var(--player-h) + 16px); }
.toast {
  display: flex; align-items: center; gap: 9px;
  padding: 10px 14px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: var(--sh-2); font-weight: 500;
  animation: toastin 0.18s ease;
  pointer-events: auto;
  max-width: min(460px, calc(100vw - 40px));
}
.toast svg { width: 16px; height: 16px; color: var(--ok-ink); }
.toast.bad { border-color: var(--error); }
.toast.bad svg { color: var(--error-ink); }
@keyframes toastin { from { opacity: 0; transform: translateY(8px); } }

/* --------------------------------------------------------------- player */

/* One row, composed left to right: transport, what is playing, the scrub with
   its two clocks, then volume and the way out. The scrub takes all the slack,
   which is what stops a wide window leaving a thousand dead pixels in the
   middle of the bar. */
.player {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  height: var(--player-h);
  display: flex; align-items: center; gap: 16px;
  padding: 0 16px;
  background: var(--sidebar);
  border-top: 1px solid var(--border-soft);
  box-shadow: 0 -6px 22px rgba(0, 0, 0, 0.22);
}
.player-left { display: flex; align-items: center; gap: 12px; flex: 0 1 300px; min-width: 0; }
.play-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; flex: none;
  border-radius: 50%; border: 1px solid var(--accent-line);
  background: var(--accent-soft); color: var(--accent); cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, transform 0.1s ease;
}
.play-btn:hover { background: var(--accent); color: var(--accent-ink); }
.play-btn:active { transform: scale(0.96); }
.play-btn:disabled { opacity: 0.45; cursor: default; }
.play-btn svg { width: 20px; height: 20px; }

.player-track { min-width: 0; }
.player-name {
  font-weight: 600; color: var(--text-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.player-where {
  font-size: var(--fs-meta); color: var(--faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.player-mid { display: flex; align-items: center; gap: 12px; flex: 1 1 auto; min-width: 0; }
.time { font-size: var(--fs-meta); color: var(--muted); font-variant-numeric: tabular-nums; flex: none; min-width: 40px; }
.time.right { text-align: right; }
.player-right { display: flex; align-items: center; gap: 12px; flex: none; }
.player-vol { display: flex; align-items: center; gap: 8px; color: var(--icon); }
.player-vol svg { width: 17px; height: 17px; }

.player-error {
  display: flex; align-items: center; gap: 10px;
  flex: 1 1 auto; min-width: 0;
  color: var(--warn-ink);
}
.player-error svg { width: 16px; height: 16px; }
.player-error > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* One range control, styled the same in both engines. Visible fill, visible
   handle at rest: a hairline with a hover-only thumb reads as a divider, not
   as something you can drag. */
.range {
  -webkit-appearance: none; appearance: none;
  flex: 1 1 auto; height: 18px; margin: 0; background: none; cursor: pointer;
  min-width: 60px;
}
.range::-webkit-slider-runnable-track {
  height: 5px; border-radius: 3px;
  background: linear-gradient(to right, var(--accent) var(--pct, 0%), var(--well) var(--pct, 0%));
}
.range::-moz-range-track { height: 5px; border-radius: 3px; background: var(--well); }
.range::-moz-range-progress { height: 5px; border-radius: 3px; background: var(--accent); }
.range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 13px; height: 13px; margin-top: -4px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--sidebar);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  transition: transform 0.1s ease;
}
.range::-moz-range-thumb {
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--sidebar);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.range:hover::-webkit-slider-thumb, .range:focus-visible::-webkit-slider-thumb { transform: scale(1.18); }
.range:disabled { cursor: default; }
.range:disabled::-webkit-slider-thumb { opacity: 0; }
.range:disabled::-moz-range-thumb { opacity: 0; }
.range.vol { flex: none; width: 86px; }

.player.loading .player-name::after {
  content: ''; display: inline-block; width: 9px; height: 9px; margin-left: 8px;
  border: 1.5px solid var(--muted); border-top-color: transparent; border-radius: 50%;
  animation: spin 0.7s linear infinite; vertical-align: -1px;
}

/* ------------------------------------------------------------ responsive */

@media (max-width: 1080px) {
  .thead, .row, .skeleton-row { grid-template-columns: 1fr 104px 100px; }
  .thead .col-mod, .row .col-mod, .skeleton-row .col-mod { display: none; }
  .share-row { grid-template-columns: 1fr 116px 96px; }
  .share-row .col-mod { display: none; }
  .search { flex: 0 1 230px; }
}

@media (max-width: 760px) {
  :root { --sidebar-w: 0px; --header-h: 56px; --row-h: 56px; }

  .shell { grid-template-columns: 1fr; }

  /* The sidebar becomes a drawer. It keeps its DOM position so focus order
     stays sane, and the scrim is a sibling button so a tap outside closes it. */
  .sidebar {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 100;
    width: min(304px, 86vw);
    transform: translateX(-102%);
    transition: transform 0.2s ease;
    box-shadow: var(--sh-3);
  }
  body.nav-open .sidebar { transform: none; }
  .nav-scrim {
    position: fixed; inset: 0; z-index: 95;
    background: var(--scrim); border: none; padding: 0; cursor: pointer;
    animation: fade 0.16s ease;
  }
  .main { grid-column: 1; }
  .nav-toggle { display: inline-flex; }

  /* Two rows: the trail stays (it is the only way back up a deep tree) and the
     search gets the full width under it rather than fighting for 150px. */
  .topbar { padding: 8px 12px; gap: 8px; flex-wrap: wrap; }
  .crumbs { order: 1; flex: 1 1 auto; }
  .topbar-actions { order: 2; }
  .search { order: 3; flex: 1 1 100%; min-width: 0; }
  /* Icon only, so the folder actions ride on the breadcrumb row instead of
     taking a third row off a 812px screen. The aria-label carries the name. */
  .topbar-actions .btn { padding: 0 9px; }
  .topbar-actions .btn span { display: none; }

  .page-pad { padding: 14px 10px 32px; }
  .page-head { padding: 0 8px; }
  .ro-banner, .quota-banner { margin: 12px 10px 0; }

  .thead { display: none; }
  .row, .skeleton-row { grid-template-columns: 1fr auto; padding: 6px 8px; }
  .row .col-size, .row .col-mod,
  .skeleton-row .col-size, .skeleton-row .col-mod { display: none; }
  .row-sub-compact { display: block; }

  .share-row { grid-template-columns: 1fr auto; padding: 10px 8px; }
  .share-row .col-size, .share-row .col-mod { display: none; }

  .signin { padding: 16px; }
  .signin h1 { font-size: var(--fs-title); }
  .signin .lede { font-size: var(--fs-body); }
  .signin-card { padding: 18px; }

  .player { padding: 0 10px; gap: 10px; }
  .player-left { flex: 0 1 auto; }
  .player-track { display: none; }
  .player-right .player-vol { display: none; }
  .toasts { left: 10px; right: 10px; }
}

/* --------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .sk { background: var(--hover); }
}
