/* Inter is loaded from Google Fonts so the vuwa wordmark + the rest
 * of the body type render identically across platforms. We pull
 * weights 400 + 500 (the only two we use) plus the swash 'ss01'
 * stylistic set. Falls through to the system stack if the network
 * blocks Google Fonts. */
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap");

/* The HTML `hidden` attribute SHOULD always hide an element, but the
 * UA stylesheet's `[hidden] { display: none }` is zero-specificity, so
 * any class-level `display: flex/grid/block` silently overrides it.
 * This `!important` rule restores the spec-intended behaviour
 * globally — once it's here, you never have to write per-class
 * `.foo[hidden] { display: none !important }` again. */
[hidden] { display: none !important; }

:root {
  --bg: #0e0e10;
  --fg: #e9e9ec;
  --muted: #8a8a92;
  /* Muted teal accent. Replaces the amber #f4c76b that the old
   * theme used everywhere — primary buttons, focus rings, the
   * compare-divider handle, the nav active-indicator. Tuned to
   * read as "tone, not signal" against the dark UI while keeping
   * enough chroma to feel teal. WCAG-AA legible with #0a0c0e
   * text on top. Iteration history:
   *   #f4c76b amber               (replaced)
   *   #9adfcf bright pastel       (too bright)
   *   #3aa392 mid teal            (still too bright + saturated)
   *   #3a6b63 muted dark teal     (too dark)
   *   #3d7d72 current */
  --accent: #3d7d72;
  --divider: #3d7d72;

  /* ---- Cool teal/cyan/green palette ----------------------------------
   * Used by the library card pills (type / status / tags) + the kind-
   * icon film-stock badges. Keeps the chrome elsewhere on the original
   * amber accent for now; pills carry the saturation. */
  --teal:    #4dd4bf;
  --cyan:    #5fc8d1;
  --green:   #82d97c;
  --sage:    #b8c9c2;

  /* ---- Reusable film-grain layer -------------------------------------
   * High-frequency fractal noise baked into a 60×60 SVG, alpha kept low
   * so the grain reads as texture rather than pattern. Stacked over
   * the radial light-effect gradients on every clip-card thumbnail. */
  --film-grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='60' height='60'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.95' numOctaves='2' seed='6'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.45 0'/></filter><rect width='100%' height='100%' filter='url(%23n)' opacity='0.55'/></svg>");

  /* Per-kind film-stock canister tones — each kind sits on a slightly
   * different green-teal so the four kinds read as "different stocks
   * of the same family" once the grain is layered on top. */
  --film-bg-video: #0c2826;
  --film-bg-photo: #0e2418;
  --film-bg-audio: #0d2421;
  --film-bg-doc:   #15201d;
}

* { box-sizing: border-box; }

/* Root font-size sets the size of `1rem`, which most of our spacing,
 * card-padding, and type scales off. Dropping from the browser
 * default (16px) to 14px scales everything ~12% smaller in one
 * place — looks closer to the proportions the UI had before the
 * production-deploy environment shift made the chrome feel oversized.
 * Px-based sizes (icons, fixed-pixel borders) stay exactly the same;
 * only rem-relative values get tighter. */
html {
  font-size: 14px;
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--fg);
  /* Inter first, then system stack as fallback for when Google
   * Fonts hasn't loaded yet (no FOUT — the system fonts are close
   * enough metrically that the swap is barely visible). */
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Tell the browser this UI is dark-themed so native form widgets
   * (option dropdowns, scrollbars, autofill chrome) stop using their
   * bright-white defaults. */
  color-scheme: dark;
}
/* (Earlier we tried a sticky-footer flex-column body to keep the
 * footer at the bottom of the viewport on short pages. That fix
 * was a symptom-treatment for an overflow bug — the
 * .workspace__main max-height cap on the player page was making
 * the library content paint past its parent box, landing on top of
 * the footer. Removing that max-height cap fixes the underlying
 * problem without needing flex-column on body, which was breaking
 * the .app__header layout via cross-axis margin: auto. Body stays
 * a normal block container.) */

.accent { color: var(--accent); }

header {
  padding: 2.5rem 1.5rem 1.25rem;
  text-align: center;
  max-width: 900px;
  margin: 0 auto;
}

header h1 {
  font-size: clamp(1.4rem, 2.5vw, 2rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 0 0 0.75rem;
  line-height: 1.25;
}

header .sub {
  color: var(--muted);
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.6;
  max-width: 680px;
  margin-inline: auto;
}

header .sub strong {
  color: var(--fg);
  font-weight: 500;
}

/* Generic <main> shell. 1540px ceiling (≈8% wider than the previous
 * 1425) gives the player a bit more breathing room on big displays
 * without ever sprawling edge-to-edge. The 2rem side padding keeps
 * content off the screen edges on smaller windows. */
main {
  padding: 1rem 2rem;
  max-width: 1540px;
  margin: 0 auto;
}

.compare {
  position: relative;
  width: 100%;
  /* The aspect-ratio is set per-clip from review.js via the
   * --asset-aspect custom property — 9 / 16 for vertical, 1 / 1 for
   * square, 16 / 9 (the default) for everything else. The server
   * encodes non-9:16/1:1 sources padded into a 16:9 canvas, so the
   * 16:9 default matches what's in the file. */
  aspect-ratio: var(--asset-aspect, 16 / 9);
  background: #000;
  overflow: hidden;
  user-select: none;
  touch-action: none;
  border-radius: 8px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
  cursor: ew-resize;
  isolation: isolate; /* contain mix-blend-mode used by difference view */
}
/* When the active clip is portrait or square, cap the player's max
 * width so a tall 9:16 video doesn't stretch to fill the whole
 * left column on a wide monitor. The cap is computed from a
 * sensible vertical budget so the comments column on the right
 * still has room and the page doesn't have to scroll. The whole
 * .player wrapper (controls, caption, etc.) shrinks with the
 * .compare so the chrome lines up under the video, not under empty
 * space. */
.player[data-aspect="portrait"] {
  max-width: calc((100vh - 12rem) * 9 / 16);
  margin: 0 auto;
}
.player[data-aspect="square"] {
  max-width: calc((100vh - 12rem));
  margin: 0 auto;
}
/* The zoom wrapper holds every visual layer (videos + LUT canvases +
 * SVG markup) so a single CSS transform scales them in lockstep. The
 * wrapper fills .compare exactly; the transform translates + scales
 * around its top-left corner. `will-change` is set per-state in JS:
 * promoted only while zoomed so a 1× idle player doesn't pay the
 * compositing-layer + re-rasterise cost on every video frame. */
.compare__zoom {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
}
/* Hand-tool cursor when a non-1x zoom is active. Plain "grab" while
 * idle, "grabbing" mid-drag. The .is-zoomed flag also hides the wipe
 * divider — wiping a zoomed-in frame is rarely what you want, and the
 * divider's pointer area would fight with the pan drag. */
.compare.is-hand-mode { cursor: grab; }
.compare.is-hand-mode.is-panning { cursor: grabbing; }
.compare.is-zoomed .divider { display: none; }
/* Vertical wipe: change the surface cursor to up/down so the user
 * gets a visual cue that drag-to-wipe runs along the Y axis here. */
.compare[data-wipe-axis="v"] { cursor: ns-resize; }

/* Photo layer — sits at the same position the underlying <video>
 * would, with object-fit: contain so the image letterboxes inside
 * .compare exactly like a video. The element is hidden via the
 * [hidden] attribute when the active clip is a video; review.js
 * + share.html flip it on for photo clips. */
.compare .photo-layer,
.share-player .photo-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
  pointer-events: none;
}
.compare .photo-layer[hidden],
.share-player .photo-layer[hidden] { display: none; }

/* Photo asset mode: hide the video-only controls (transport bar,
 * mode tabs, scrubber). The compare wrapper still owns the markup +
 * LUT canvases so those keep working on a photo. */
.compare[data-asset="photo"] video { display: none; }
.compare[data-asset="photo"] .divider { display: none; }
.compare[data-asset="photo"] { cursor: default; }

/* Non-video kinds (audio / pdf / text / zip): hide the <video>,
 * the wipe divider, and any markup overlay. The compare wrapper
 * keeps owning sizing so the layered viewer fills the same slot
 * the player would. */
.compare[data-asset="audio"] video,
.compare[data-asset="pdf"]   video,
.compare[data-asset="text"]  video,
.compare[data-asset="zip"]   video { display: none; }
.compare[data-asset="audio"] .divider,
.compare[data-asset="pdf"]   .divider,
.compare[data-asset="text"]  .divider,
.compare[data-asset="zip"]   .divider { display: none; }
.compare[data-asset="audio"] .anno-layer,
.compare[data-asset="pdf"]   .anno-layer,
.compare[data-asset="text"]  .anno-layer,
.compare[data-asset="zip"]   .anno-layer { display: none; }
.compare[data-asset="audio"] .lut-overlay,
.compare[data-asset="pdf"]   .lut-overlay,
.compare[data-asset="text"]  .lut-overlay,
.compare[data-asset="zip"]   .lut-overlay { display: none; }
.compare[data-asset="audio"],
.compare[data-asset="pdf"],
.compare[data-asset="text"],
.compare[data-asset="zip"] { cursor: default; }

/* Audio layer — purpose-built layout with a big waveform front and
 * centre. Header carries the filename + format metadata, waveform
 * fills the remaining space, time readout sits below it, native
 * <audio controls> at the bottom. The waveform supports click-to-
 * seek and renders a moving playhead via .audio-layer__playhead. */
.compare .audio-layer,
.share-player .audio-layer {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-rows: auto 1fr auto auto;
  gap: 16px;
  padding: 28px clamp(20px, 6vw, 72px);
  background:
    radial-gradient(120% 120% at 50% 0%, rgba(77,212,191,0.06) 0%, transparent 60%),
    linear-gradient(180deg, #0e1a1f 0%, #0a0f12 100%);
}
.compare .audio-layer[hidden],
.share-player .audio-layer[hidden] { display: none; }

/* Header: filename + format metadata. Stacked so the title gets
 * room to breathe and the metadata line ("WAV · 48 kHz · stereo
 * · 03:48") sits directly underneath. */
.audio-layer__head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.audio-layer__title {
  color: #e8ecef;
  font-size: 18px;
  font-weight: 500;
  letter-spacing: -0.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.audio-layer__meta {
  color: #95a0a8;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  display: flex;
  gap: 0.45rem;
  flex-wrap: wrap;
  justify-content: center;
}
.audio-layer__meta__sep {
  color: #2d333c;
}

/* Waveform: big, fills the remaining vertical space. Click to seek
 * (the JS attaches a pointer-down listener on the wrap). The wrap
 * is `position: relative` so the playhead can absolutely position
 * inside it. */
.audio-layer__wave-wrap {
  position: relative;
  width: 100%;
  background: rgba(77,212,191,0.04);
  border: 1px solid rgba(77,212,191,0.18);
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  /* min-height so it always feels prominent even on a phone where
   * the player column is short. */
  min-height: 140px;
}
.audio-layer__wave {
  display: block;
  width: 100%;
  height: 100%;
  /* Fills the wrap; the JS sets the pixel buffer to dpr-scaled
   * dimensions so the strokes render crisply on retina screens. */
}
/* Playhead: a thin teal vertical line driven by JS via inline
 * `left: <pct>%`. Pseudo-element on top adds a subtle 1px shadow
 * so the line still reads against bright sections of the wave. */
.audio-layer__playhead {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: #4dd4bf;
  box-shadow: 0 0 6px rgba(77,212,191,0.55);
  pointer-events: none;
  transform: translateX(-50%);
  left: 0;
  transition: left 90ms linear;
}
.audio-layer__playhead::before {
  content: "";
  position: absolute;
  top: -4px;
  left: 50%;
  transform: translateX(-50%);
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #4dd4bf;
}

.audio-layer__times {
  display: flex;
  justify-content: space-between;
  color: #95a0a8;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  padding: 0 0.25rem;
}

.audio-layer audio {
  width: 100%;
  /* Style the native audio bar to match the page's dark theme. */
  filter: invert(0.92) hue-rotate(180deg);
}

/* PDF layer — full-bleed iframe; the browser's native viewer fills
 * the slot. */
.compare .pdf-layer,
.share-player .pdf-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  background: #1f1f24;
}
.compare .pdf-layer[hidden],
.share-player .pdf-layer[hidden] { display: none; }

/* Text layer — header bar + scrolling <pre>. */
.compare .text-layer,
.share-player .text-layer {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: #11161e;
  color: #e5e7eb;
  font-family: ui-sans-serif, system-ui, sans-serif;
}
.compare .text-layer[hidden],
.share-player .text-layer[hidden] { display: none; }
.text-layer__bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border-bottom: 1px solid rgba(255,255,255,0.07);
  font-size: 12px;
  color: #9ca3af;
}
.text-layer__name { font-weight: 600; color: #e5e7eb; }
.text-layer__wrap {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
}
.text-layer__body {
  flex: 1;
  margin: 0;
  padding: 12px 16px;
  overflow: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.5;
  white-space: pre;
}
.text-layer__body[data-wrap="1"] {
  white-space: pre-wrap;
  word-break: break-word;
}

/* Zip layer — toolbar + scrollable list of entries. */
.compare .zip-layer,
.share-player .zip-layer {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: #11161e;
  color: #e5e7eb;
  font-family: ui-sans-serif, system-ui, sans-serif;
}
.compare .zip-layer[hidden],
.share-player .zip-layer[hidden] { display: none; }
.zip-layer__bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid rgba(255,255,255,0.07);
  font-size: 12px;
  color: #cbd5e1;
}
.zip-layer__name {
  font-weight: 600;
  color: #fde68a;
  margin-right: auto;
}
.zip-layer__btn {
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.12);
  color: #e5e7eb;
  padding: 4px 10px;
  border-radius: 4px;
  cursor: pointer;
  font: inherit;
  font-size: 12px;
}
.zip-layer__btn:hover { background: rgba(255,255,255,0.12); }
.zip-layer__btn--primary {
  background: #2563eb;
  border-color: #2563eb;
  color: #fff;
}
.zip-layer__btn--primary:disabled {
  background: rgba(37,99,235,0.4);
  cursor: not-allowed;
  color: rgba(255,255,255,0.5);
}
.zip-layer__tree {
  flex: 1;
  overflow: auto;
  padding: 4px 0;
}
.zip-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px;
  font-size: 13px;
  cursor: pointer;
}
.zip-row:hover { background: rgba(255,255,255,0.04); }
.zip-row--unsupported { opacity: 0.45; cursor: not-allowed; }
.zip-row__icon { width: 18px; text-align: center; }
.zip-row__name { flex: 1; min-width: 0; word-break: break-all; }
.zip-row__size {
  color: #9ca3af;
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  min-width: 64px;
  text-align: right;
}
.zip-row__download {
  color: #93c5fd;
  text-decoration: none;
  font-size: 12px;
  padding: 0 4px;
}
.zip-row__download:hover { text-decoration: underline; }

.compare video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
  display: block;
  pointer-events: none; /* let the compare container catch drags */
}

.overlay {
  position: absolute;
  inset: 0;
  /* Sit above .lut-overlay--right (z-index 1). The LUT canvas applies a
   * grade to the right video and covers the full compare area; without
   * an explicit z-index here the wipe's left-half (which holds the
   * left video + its own LUT canvas) was painting underneath the right
   * canvas, so wipe + difference + side-by-side modes all looked dead
   * the moment a viewing LUT was applied. The labels (z-index 4) and
   * divider (z-index 5) still sit on top. */
  z-index: 2;
  clip-path: inset(0 50% 0 0);
  -webkit-clip-path: inset(0 50% 0 0);
  will-change: clip-path;
}

.label {
  position: absolute;
  top: 1rem;
  padding: 0.35rem 0.7rem;
  background: rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg);
  border-radius: 4px;
  pointer-events: none;
  font-weight: 500;
  /* Sit above .lut-overlay--right (z-index 1). The LUT canvas applies a
   * grade across the full compare and would otherwise paint over the
   * version label — most visibly the "older version" / left label
   * when a LUT is active in compare modes. Stay below the divider's
   * z-index 5 so the wipe handle still grabs cleanly across the
   * label. */
  z-index: 4;
}

.label--left  { left: 1rem; }
.label--right { right: 1rem; }

.divider {
  position: absolute;
  background: var(--divider);
  touch-action: none;
  z-index: 5;
  /* Default = horizontal wipe (vertical bar that slides L↔R). The
   * .compare[data-wipe-axis="v"] block below flips this to a
   * horizontal bar that slides up↕down. */
  top: 0; bottom: 0;
  left: 50%;
  width: 2px;
  transform: translateX(-50%);
  cursor: ew-resize;
}
/* Vertical-axis wipe: divider becomes a horizontal bar; the JS
 * positions it via `style.top` instead of `style.left`. */
.compare[data-wipe-axis="v"] .divider {
  top: 50%;
  left: 0; right: 0;
  bottom: auto;
  width: auto;
  height: 2px;
  transform: translateY(-50%);
  cursor: ns-resize;
}
/* The handle's two grip lines rotate so they read as a horizontal
 * grip when the bar is horizontal. */
.compare[data-wipe-axis="v"] .divider__handle span {
  width: 14px;
  height: 2px;
}

.divider:focus { outline: none; }
.divider:focus .divider__handle {
  box-shadow: 0 0 0 4px rgba(244, 199, 107, 0.35), 0 2px 10px rgba(0, 0, 0, 0.6);
}

.divider__handle {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 44px;
  height: 44px;
  background: var(--divider);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
  transition: box-shadow 120ms ease;
}

/* "<-->" icon inside the divider handle. Rotates 90° via CSS when the
 * wipe axis flips to vertical, so the icon always points along the
 * drag direction. Inherits the handle's foreground colour for crisp
 * contrast against the accent-yellow circle. */
.divider__handle__icon {
  display: block;
  color: #0e0e10;
  transition: transform 150ms ease;
  pointer-events: none;
}
.compare[data-wipe-axis="v"] .divider__handle__icon {
  transform: rotate(90deg);
}

.hint {
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
  margin: 1rem 0 0;
}

footer {
  padding: 3rem 1.5rem 4rem;
  text-align: center;
  color: var(--muted);
}

footer .tagline {
  font-size: 0.95rem;
  margin: 0 0 1rem;
}

footer .cta {
  color: var(--accent);
  text-decoration: none;
  font-weight: 500;
  font-size: 1rem;
  border-bottom: 1px solid transparent;
  transition: border-color 120ms ease;
}

footer .cta:hover { border-bottom-color: var(--accent); }

/* Respect reduced motion preference — don't autoplay big videos */
@media (prefers-reduced-motion: reduce) {
  .compare video { animation: none; }
}

/* Mobile tweaks */
@media (max-width: 640px) {
  header { padding: 1.5rem 1rem 0.75rem; }
  .label { font-size: 0.65rem; padding: 0.25rem 0.5rem; top: 0.5rem; }
  .label--left  { left: 0.5rem; }
  .label--right { right: 0.5rem; }
}

/* =========================================================================
   Player — wrapper, view modes, transport controls
   ========================================================================= */

.player {
  width: 100%;
  /* No max-width: the player fills the workspace's main column so the
   * video matches the library reel below it (both occupy the full
   * stage width). The old 1200px cap was leftover from when the page
   * shell was 1280px wide; with the shell now 1900px the cap was
   * leaving empty space on either side of the video and making the
   * library reel below visibly wider than the player + comments row. */
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* Non-wipe modes don't need the resize cursor — nothing to drag. */
.compare[data-mode="difference"],
.compare[data-mode="side"],
.compare[data-mode="only1"],
.compare[data-mode="only2"] { cursor: default; }

/* ---- Difference view ----------------------------------------------------
   Overlay ignores the wipe clip-path and difference-blends against the base
   video. With identical sources the result is pure black.

   We deliberately do NOT apply a brightness/contrast filter on .compare here.
   An ancestor filter creates a second stacking context on top of the one from
   isolation: isolate, and in combination with mix-blend-mode some browsers
   (Safari, older Chromium) stop honouring the difference blend — the result
   visually resembles a multiply. Keep the stack simple and let the raw
   difference render. If amplification for subtle compression drift is needed
   later, add it as an explicit UI toggle, not a side-effect of this mode. */
.compare[data-mode="difference"] .overlay {
  clip-path: none;
  -webkit-clip-path: none;
  mix-blend-mode: difference;
}
.compare[data-mode="difference"] .divider,
.compare[data-mode="difference"] .label { display: none; }

/* ---- Side-by-side -------------------------------------------------------
   Each video occupies half the container and scales to fit inside that half
   with object-fit: contain — so the full frame is always visible, letterboxed
   if aspect ratios don't match the half.

   videoRight is a replaced element, so `width: auto` would make it revert to
   its *intrinsic* pixel size (e.g. 1920px for a 1920x1080 video) instead of
   filling the remaining half — and overflow:hidden on .compare would clip it,
   appearing zoomed in. Pin width:50% explicitly so the video sizes to half
   the container and object-fit:contain letterboxes inside it.

   IMPORTANT: every visual layer that sits over the right video has to be
   constrained to the right half too. The wrapping pattern here is:
     - videoRight + every .lut-overlay--right + every .overlay--right-only
       layer is pinned to the right half.
     - .overlay (the wipe overlay containing videoLeft + the left LUT canvas
       + future left-side layers) is pinned to the left half — anything
       INSIDE .overlay inherits the half-width clip for free.
   Without this, an overlay added without a matching side-by-side rule
   silently paints across both halves and looks like "the right video isn't
   scaling down" — which is exactly the bug we keep fighting. New right-side
   visual layers should match the selector list below or be wrapped in .overlay. */
.compare[data-mode="side"] #videoRight,
.compare[data-mode="side"] .lut-overlay--right,
.compare[data-mode="side"] [data-side="right"] {
  left: 50%;
  right: 0;
  top: 0;
  bottom: 0;
  width: 50%;
  height: 100%;
  clip-path: none;
  -webkit-clip-path: none;
}
.compare[data-mode="side"] .overlay {
  inset: 0 50% 0 0;
  clip-path: none;
  -webkit-clip-path: none;
}
.compare[data-mode="side"] .divider { display: none; }
.compare[data-mode="side"] .label--left  { left: 1rem; }
.compare[data-mode="side"] .label--right { right: 1rem; }

/* Subtle inner dividing line between the two halves */
.compare[data-mode="side"]::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  width: 1px;
  background: #2c2c31;
  z-index: 4;
  pointer-events: none;
}

/* ---- Solo modes --------------------------------------------------------
   "File 1 only" shows videoLeft full-frame by dropping the wipe clip-path on
   the overlay. "File 2 only" hides the overlay entirely so videoRight shows
   through on its own. Either way, no divider and only one label. */
.compare[data-mode="only1"] .overlay {
  clip-path: none;
  -webkit-clip-path: none;
}
.compare[data-mode="only1"] .divider,
.compare[data-mode="only1"] .label--right { display: none; }

.compare[data-mode="only2"] .overlay,
.compare[data-mode="only2"] .divider,
.compare[data-mode="only2"] .label--left { display: none; }

/* ---- Toolbar: mode tabs + fullscreen ----------------------------------- */
.player__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.player__modes {
  display: inline-flex;
  flex-wrap: wrap;
  background: #0e0e10;
  border: 1px solid #2c2c31;
  border-radius: 8px;
  padding: 3px;
  gap: 3px;
}

.player-mode-btn {
  font: inherit;
  font-size: 0.85rem;
  color: var(--fg);
  background: transparent;
  border: 0;
  border-radius: 5px;
  padding: 0.45rem 0.9rem;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.player-mode-btn:hover { background: #1f1f23; }
.player-mode-btn.is-active {
  background: var(--accent);
  color: #0e0e10;
}

/* ---- Controls bar ------------------------------------------------------- */
.player__controls {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.65rem;
  background: #17171a;
  border: 1px solid #2c2c31;
  border-radius: 8px;
  flex-wrap: wrap;
}

.player__group {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

.player-btn {
  font: inherit;
  color: var(--fg);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 5px;
  cursor: pointer;
  min-width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 0.35rem;
  transition: background 120ms ease, border-color 120ms ease;
}
.player-btn:hover { background: #1f1f23; }
.player-btn:focus-visible { outline: none; border-color: var(--accent); }

.player-btn svg {
  width: 16px;
  height: 16px;
  fill: currentColor;
  display: block;
}

/* Icon toggle based on button state */
.player-btn[data-playing="1"] .icon--play  { display: none; }
.player-btn[data-playing="0"] .icon--pause { display: none; }
.player-btn[data-muted="1"]   .icon--volume-on  { display: none; }
.player-btn[data-muted="0"]   .icon--volume-off { display: none; }
.player-btn[data-fs="1"] .icon--fs-enter { display: none; }
.player-btn[data-fs="0"] .icon--fs-exit  { display: none; }

/* Active toggle state for sticky buttons (loop, mute when off, etc.).
 * The colored background + accent fill make on/off visually
 * unmistakable — without this the loop button silently flipped its
 * data-loop attribute and the user couldn't tell. */
.player-btn.is-active {
  background: rgba(245, 166, 35, 0.18);
  border-color: rgba(245, 166, 35, 0.55);
  color: var(--accent, #f5a623);
}
.player-btn.is-active:hover {
  background: rgba(245, 166, 35, 0.26);
}
.player-btn.is-active svg { fill: currentColor; }
/* Next-clip "you're at the last clip" hint — not disabled (clicking
 * still wraps), just dimmed so the user notices. */
.player-btn.is-end {
  opacity: 0.55;
}

.player__time {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 0.82rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  min-width: 96px;
  text-align: center;
  user-select: none;
}

/* Range inputs (scrubber + volume) ---------------------------------------- */
.player input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  cursor: pointer;
  height: 18px;
  margin: 0;
}
.player__scrub {
  flex: 1;
  min-width: 120px;
}
/* Wrapper that lets us float comment markers over the scrubber. The
 * wrapper is created at runtime by player.js (the static markup stays
 * just <input class="player__scrub">), so don't restyle the input
 * itself — leave it to the existing range-input rules. */
.player__scrub-wrap {
  flex: 1;
  min-width: 120px;
  position: relative;
  display: flex;
  align-items: center;
}
.player__scrub-wrap .player__scrub { flex: 1; min-width: 0; }
.player__scrub-markers {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.player__scrub-marker {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #5b8cff;
  border: 1.5px solid #0e0e10;
  padding: 0;
  cursor: pointer;
  pointer-events: auto;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  transition: transform 120ms ease, background 120ms ease;
}
.player__scrub-marker:hover,
.player__scrub-marker:focus-visible {
  background: var(--accent, #f4c76b);
  transform: translate(-50%, -50%) scale(1.3);
  outline: none;
}
.player__volume { width: 90px; }

/* Persistent resolution + dynamic-range pill in the transport row.
 * SDR variant: neutral muted grey, just-there-enough to read at a
 * glance without competing with the playback controls. HDR variant:
 * warm gold-orange gradient (matches the cog's HDR badge so the two
 * surfaces feel like one design language) — visually distinct so
 * colour-critical reviewers can see HDR engagement at a glance. */
.player__dynamic-range {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 8px;
  border-radius: 11px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  background: #1c1c22;
  color: #a4a9b3;
  border: 1px solid #2a2f3a;
  user-select: none;
  white-space: nowrap;
}
.player__dynamic-range.is-hdr {
  background: linear-gradient(135deg, #d97b1a 0%, #c2570d 100%);
  color: #fff;
  border-color: transparent;
}

.player input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  background: #2c2c31;
  border-radius: 2px;
}
.player input[type="range"]::-moz-range-track {
  height: 4px;
  background: #2c2c31;
  border-radius: 2px;
  border: 0;
}
.player input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  background: var(--accent);
  border-radius: 50%;
  margin-top: -5px;
  border: 0;
  cursor: pointer;
}
.player input[type="range"]::-moz-range-thumb {
  width: 14px;
  height: 14px;
  background: var(--accent);
  border-radius: 50%;
  border: 0;
  cursor: pointer;
}
.player input[type="range"]:focus-visible {
  outline: none;
}
.player input[type="range"]:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px rgba(244, 199, 107, 0.3);
}

/* ---- Fullscreen -------------------------------------------------------- */
.player:fullscreen,
.player:-webkit-full-screen {
  background: var(--bg);
  padding: 0.75rem;
  max-width: none;
  height: 100vh;
}
.player:fullscreen .compare,
.player:-webkit-full-screen .compare {
  flex: 1 1 auto;
  aspect-ratio: auto;
  min-height: 0;
  border-radius: 0;
}

/* ---- Mobile ------------------------------------------------------------- */
@media (max-width: 720px) {
  .player__toolbar { flex-direction: column; align-items: stretch; }
  .player__modes   { align-self: flex-start; }
  .player__time    { min-width: 82px; font-size: 0.72rem; }
  .player__volume  { width: 60px; }
}

/* =========================================================================
   Top nav (shared across authed pages)
   ========================================================================= */
.topnav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(14, 14, 16, 0.85);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid #1f1f24;
}
.topnav__inner {
  max-width: 1540px;
  margin: 0 auto;
  padding: 0.55rem 2rem;
  display: flex;
  align-items: center;
  gap: 1.5rem;
}
.topnav__brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--fg);
  text-decoration: none;
  font-weight: 500;
  font-size: 1.25rem;
  letter-spacing: -0.01em;
}
/* Legacy class — kept around so any old rendered nav HTML still
 * reads cleanly. The new mark uses .vuwa-mark below. */
.topnav__dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px rgba(61, 125, 114, 0.18);
}

/* ---- vuwa wordmark + trailing play triangle ----------------------
 * Used in the top nav (logged-in chrome) and on the marketing /
 * login pages. Lowercase wordmark in Inter Medium with tightened
 * tracking; the play triangle reads as the brand's "ear" — sized
 * to roughly the x-height of the wordmark and tinted cyan so it
 * stays a glyph rather than disappearing into the type. */
.vuwa-mark {
  display: inline-flex;
  align-items: center;
  /* Breathing room between the wordmark and the play triangle —
   * lives on the lockup itself so EVERY surface (topnav, share
   * header, login, anywhere) matches without each page having to
   * remember to add its own gap. Em-sized so it scales with the
   * surrounding font-size. */
  gap: 0.4em;
  text-decoration: none;
  color: var(--fg);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
  font-weight: 500;
  letter-spacing: -0.04em;
  font-feature-settings: "ss01";
}
.vuwa-mark__word { color: inherit; }
.vuwa-mark__play {
  display: inline-flex;
  align-items: center;
  color: #5fc8d1;
}
.vuwa-mark__play svg {
  display: block;
  width: 0.62em;
  height: 0.7em;
}
/* ---------- Org switcher (active-workspace picker) ----------------- */
.topnav__org-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin-left: 0.6rem;
  /* More vertical padding so brand logos with no internal margin
   * (like the WASH wordmark which fills its image canvas) get
   * proper breathing room inside the pill. */
  padding: 0.5rem 0.75rem;
  background: #18181c;
  color: var(--fg);
  border: 1px solid #262a31;
  border-radius: 8px;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  max-width: 200px;
}
.topnav__org-btn:hover   { background: #1f1f24; border-color: #313640; }
.topnav__org-btn[aria-expanded="true"] { background: #1f1f24; border-color: var(--accent); }
.topnav__org-btn__name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 160px;
}
.topnav__org-btn__chev { color: var(--muted); font-size: 0.7rem; }
/* Active org's brand logo, shown to the left of the name when set.
 * Sized to fit the button row's height (~22px tall) and capped at
 * 80px wide so a long horizontal lockup doesn't push the name out. */
.topnav__org-btn__logo {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  margin-right: 0.4rem;
}
.topnav__org-btn__logo img {
  /* Defaults — overridden per-shape below. nav.js applies one of
   * .is-wide / .is-square / .is-tall to the parent slot after the
   * image loads, based on its natural aspect ratio. */
  object-fit: contain;
  display: block;
  border-radius: 4px;
  /* Sensible defaults for "I haven't been classified yet" or for
   * mid-ratio logos. Wide is the common case so we lean on its size. */
  height: 22px;
  max-width: 80px;
}
/* Wide wordmarks (≥ 1.5:1, e.g. WASH). Their visual weight comes from
 * the horizontal lockup, so keep the row's vertical footprint small. */
.topnav__org-btn__logo.is-wide img {
  height: 18px;
  max-width: 80px;
}
/* Square brand marks (~1:1). Need MORE vertical room so the glyph
 * doesn't look tiny next to the workspace name. The pill's padding
 * gives ~8px top/bottom, 26px leaves a couple of pixels of breathing
 * space and reads as a proper brand mark. */
.topnav__org-btn__logo.is-square img {
  height: 26px;
  width: 26px;
  max-width: none;
}
/* Portrait crests (≤ 0.7:1). Same logic as square — give them height,
 * keep width naturally smaller from the aspect ratio. */
.topnav__org-btn__logo.is-tall img {
  height: 26px;
  max-width: 22px;
}
/* Initial-letter fallback when the active org has no brand logo or
 * (for personal libraries) the user has no avatar uploaded. Same
 * physical size as the brand-logo image so the button doesn't reflow
 * between the two states. */
.topnav__org-btn__logo-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 4px;
  background: rgba(95, 200, 209, 0.18);
  color: var(--accent, #5fc8d1);
  font-size: 0.75rem;
  font-weight: 600;
}

/* Floating popover for per-library actions (Rename / Archive /
 * Delete). Anchored to the ⋯ button on each row in the workspace
 * dropdown's library view. */
.lib-actions-popover {
  background: #14141a;
  border: 1px solid #2a2f3a;
  border-radius: 8px;
  padding: 0.3rem;
  display: flex;
  flex-direction: column;
  min-width: 200px;
  box-shadow: 0 8px 24px -8px rgba(0,0,0,0.7);
}
.lib-actions-popover button {
  background: transparent;
  border: 0;
  color: var(--fg);
  text-align: left;
  padding: 0.5rem 0.7rem;
  font: inherit;
  font-size: 0.88rem;
  cursor: pointer;
  border-radius: 6px;
}
.lib-actions-popover button:hover { background: #1c1c22; }

/* Workspace-over-library-cap banner. Shown on the projects list when
 * a workspace has more libraries than the current plan allows
 * (paid → free downgrade scenario). Amber/orange to read as a
 * warning that needs action without being alarmist. */
.lib-overcap-banner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: rgba(244, 199, 107, 0.10);
  border: 1px solid rgba(244, 199, 107, 0.30);
  color: #f4c76b;
  padding: 0.65rem 0.9rem;
  border-radius: 8px;
  font-size: 0.88rem;
  margin: 0.5rem 0 1rem;
}
.lib-overcap-banner strong { color: #f4c76b; }
.lib-overcap-banner__link {
  margin-left: auto;
  background: #f4c76b;
  color: #2a1e00;
  font-weight: 600;
  text-decoration: none;
  padding: 0.35rem 0.75rem;
  border-radius: 6px;
}
.lib-overcap-banner__link:hover { background: #fdd57a; }

.topnav__org-menu {
  position: absolute;
  top: calc(100% + 0.35rem);
  /* Anchored under the button. JS doesn't position it, so we rely
   * on the topnav being position:relative + a left offset that lines
   * up with the brand + button. */
  left: 5.5rem;
  z-index: 60;
  min-width: 220px;
  max-width: 320px;
  background: #14141a;
  border: 1px solid #2a2f3a;
  border-radius: 10px;
  padding: 0.3rem;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
}
.topnav__org-menu__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  width: 100%;
  padding: 0.45rem 0.6rem;
  background: transparent;
  border: 0;
  border-radius: 6px;
  color: var(--fg);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  text-align: left;
}
.topnav__org-menu__item:hover    { background: #1f1f24; }
.topnav__org-menu__item.is-active{ background: rgba(61, 125, 114, 0.15); color: var(--fg); }
.topnav__org-menu__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1;
}
.topnav__org-menu__tag {
  font-size: 0.7rem;
  color: var(--muted);
  background: #232328;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.topnav__org-menu__tag--company { color: #b8c9c2; background: #1d2a28; }
/* Thin separator above the "+ New team workspace" footer button so it
 * reads as a distinct action, not just another org row. */
.topnav__org-menu__divider {
  height: 1px;
  background: #2a2f3a;
  margin: 0.3rem -0.3rem;
}
.topnav__org-menu__item--new .topnav__org-menu__logo {
  background: transparent;
  color: var(--muted);
  font-size: 1.1rem;
  font-weight: 500;
}
.topnav__org-menu__item--new .topnav__org-menu__name { color: var(--muted); }
.topnav__org-menu__item--new:hover .topnav__org-menu__name,
.topnav__org-menu__item--new:hover .topnav__org-menu__logo { color: var(--fg); }
/* Brand logo square in front of each org row in the switcher menu —
 * gives the visitor an at-a-glance "which workspace is this" while
 * scanning. Falls back to a single-letter avatar when no logo is set. */
.topnav__org-menu__logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex: 0 0 auto;
  border-radius: 5px;
  background: #1d2a28;
  overflow: hidden;
}
.topnav__org-menu__logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.topnav__org-menu__logo-fallback {
  font-size: 0.78rem;
  font-weight: 600;
  color: #b8c9c2;
  text-transform: uppercase;
}
/* Disclosure chevron on multi-library workspace rows. Signals "this
 * one expands rather than navigating", to distinguish from single-
 * library workspaces that switch + go immediately. */
.topnav__org-menu__chev {
  font-size: 1rem;
  color: var(--muted);
  margin-left: 0.1rem;
  flex: 0 0 auto;
}
.topnav__org-menu__item:hover .topnav__org-menu__chev { color: var(--fg); }
/* Library list view inside the workspace switcher dropdown — shown
 * after the user clicks a multi-library workspace. The back row
 * mirrors the `+ New workspace` footer button visually so the user
 * recognises it as navigation, not a regular row. */
.topnav__org-menu__item--back .topnav__org-menu__logo {
  background: transparent;
  color: var(--muted);
  font-size: 1.05rem;
  font-weight: 500;
}
.topnav__org-menu__item--back .topnav__org-menu__name {
  color: var(--muted);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.topnav__org-menu__item--back:hover .topnav__org-menu__name,
.topnav__org-menu__item--back:hover .topnav__org-menu__logo { color: var(--fg); }
/* Library row icon slot — folder glyph instead of brand logo. Stays
 * inside the same 24×24 square so column alignment matches the
 * workspace rows. */
.topnav__org-menu__logo--lib {
  background: rgba(95, 200, 209, 0.10);
  font-size: 0.95rem;
}
/* Section heading inside the workspace dropdown (e.g. "Libraries"
 * above the library list). Small uppercase muted label so it reads
 * as a group header, not a clickable row. Matches the back row's
 * type label so they feel like a pair. */
.topnav__org-menu__section {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0.4rem 0.7rem 0.25rem;
}
/* Project count chip on each library row. Tabular numerals so columns
 * line up no matter the digit width. Muted so the library name stays
 * the primary read. */
.topnav__org-menu__count {
  color: var(--muted);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  margin-left: 0.3rem;
  flex: 0 0 auto;
}
/* Library row container: holds the click-to-switch item + a ⋯ edit
 * button next to it. Edit button hidden by default, revealed on row
 * hover so resting state stays clean. */
.topnav__org-menu__row {
  display: flex;
  align-items: center;
  border-radius: 6px;
}
.topnav__org-menu__row:hover { background: #1c1c22; }
.topnav__org-menu__row .topnav__org-menu__item { flex: 1 1 auto; }
.topnav__org-menu__row .topnav__org-menu__item:hover { background: transparent; }
.topnav__org-menu__row.is-active .topnav__org-menu__item { color: var(--accent, #5fc8d1); }
.topnav__org-menu__edit {
  background: transparent;
  border: 0;
  color: var(--muted);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.4rem 0.5rem;
  border-radius: 4px;
  opacity: 0;
  transition: opacity 120ms ease, background 120ms ease;
  flex: 0 0 auto;
}
.topnav__org-menu__row:hover .topnav__org-menu__edit { opacity: 1; }
.topnav__org-menu__edit:hover { background: #232a36; color: var(--fg); }

/* Hero banner at the top of the projects-landing page. Shows the active
 * org's logo + name when the user is operating inside a team workspace.
 * Hidden for personal orgs (the page title already says "Projects" and
 * the user owns the only workspace so the branding is meaningless). */
.projects-hero {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin: 0 0 1.25rem;
  padding: 0.85rem 1.1rem;
  background: linear-gradient(180deg, #161620 0%, #11111a 100%);
  border: 1px solid #232a36;
  border-radius: 12px;
}
.projects-hero__logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  flex: 0 0 auto;
  border-radius: 10px;
  background: #1d2a28;
  overflow: hidden;
}
.projects-hero__logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.projects-hero__name {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--fg);
  letter-spacing: -0.01em;
}
.projects-hero__sub {
  display: block;
  font-size: 0.78rem;
  color: var(--muted);
  font-weight: 400;
  margin-top: 0.1rem;
}

/* ---------- Radio field (kept for future modal forms) ---------------- */
.field__legend {
  display: block;
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.field__radio {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
  padding: 0.55rem 0.7rem;
  border: 1px solid #23232a;
  border-radius: 8px;
  margin-bottom: 0.4rem;
  cursor: pointer;
  background: #0d0d12;
  transition: border-color 80ms ease, background 80ms ease;
}
.field__radio:hover { border-color: #3a3a44; }
.field__radio input[type="radio"] { margin-top: 0.2rem; }
.field__radio input[type="radio"]:checked ~ span { color: var(--fg); }
.field__radio:has(input:checked) {
  border-color: var(--accent);
  background: rgba(61, 125, 114, 0.06);
}
.field__radio span { display: flex; flex-direction: column; gap: 0.15rem; color: var(--fg); }
.field__radio strong { font-size: 0.9rem; font-weight: 600; }
.field__radio small  { font-size: 0.78rem; color: var(--muted); }

/* ---------- Top-nav global search omnibox ----------------------------- */
/* Pill-shaped input that grows in the middle of the topnav. Anchors a
 * grouped results dropdown directly below it. ⌘K / Ctrl-K hint sits
 * inside the input; escapes on focus. */
.topnav__search {
  position: relative;
  flex: 1 1 320px;
  max-width: 520px;
  margin-left: 0.5rem;
}
.topnav__search-input {
  width: 100%;
  background: #14141a;
  color: var(--fg);
  border: 1px solid #26262b;
  border-radius: 999px;
  padding: 0.42rem 3.2rem 0.42rem 2.1rem;
  font: inherit;
  font-size: 0.88rem;
  line-height: 1.2;
  transition: border-color 120ms ease, background 120ms ease;
}
.topnav__search-input::placeholder { color: #6a6a78; }
.topnav__search-input:focus {
  outline: none;
  border-color: #46506a;
  background: #1a1a22;
}
.topnav__search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.topnav__search-icon {
  position: absolute;
  left: 0.65rem; top: 50%;
  transform: translateY(-50%);
  color: #6a6a78;
  display: inline-flex;
  pointer-events: none;
}
.topnav__search-input:focus ~ .topnav__search-icon { color: var(--fg); }
.topnav__search-kbd {
  position: absolute;
  right: 0.55rem; top: 50%;
  transform: translateY(-50%);
  font: 600 0.7rem ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  color: #6a6a78;
  background: #1d1d22;
  border: 1px solid #2a2a33;
  border-radius: 4px;
  padding: 0.05rem 0.35rem;
  pointer-events: none;
  letter-spacing: 0.04em;
}
.topnav__search-input:focus ~ .topnav__search-kbd,
.topnav__search-input:not(:placeholder-shown) ~ .topnav__search-kbd { display: none; }

.topnav__search-results {
  position: absolute;
  left: 0; right: 0; top: calc(100% + 6px);
  background: #15151a;
  border: 1px solid #26262b;
  border-radius: 10px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
  z-index: 60;
  max-height: 70vh;
  overflow-y: auto;
  padding: 0.35rem;
}
.topnav__search-group + .topnav__search-group { margin-top: 0.35rem; padding-top: 0.35rem; border-top: 1px solid #1f1f24; }
.topnav__search-group__head {
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: #6a6a78;
  padding: 0.2rem 0.55rem 0.25rem;
}
.topnav__search-row {
  display: grid;
  grid-template-columns: 1.4rem 1fr;
  align-items: center;
  gap: 0.55rem;
  padding: 0.45rem 0.55rem;
  border-radius: 6px;
  text-decoration: none;
  color: var(--fg);
  cursor: pointer;
}
.topnav__search-row:hover,
.topnav__search-row.is-active {
  background: #1f1f28;
}
.topnav__search-row__icon {
  text-align: center;
  font-size: 0.95rem;
  color: var(--muted);
}
.topnav__search-row__body { display: flex; flex-direction: column; min-width: 0; }
.topnav__search-row__name {
  color: var(--fg);
  font-size: 0.9rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topnav__search-row__sub {
  color: #8c94a0;
  font-size: 0.78rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topnav__search-empty {
  color: #8c94a0;
  font-size: 0.85rem;
  margin: 0;
  padding: 0.6rem 0.8rem;
  text-align: center;
}
@media (max-width: 720px) {
  .topnav__search { flex: 1 1 0; max-width: none; }
  .topnav__search-kbd { display: none; }
}

.topnav__right {
  margin-left: auto;
  position: relative;
  /* flex so every icon button + the account pill sit on the same
   * vertical centre line. Without this the children fall back to
   * inline flow, which baseline-aligns the icons against the
   * taller .topnav__account pill and visually drops them low. */
  display: flex;
  align-items: center;
  gap: 0.25rem;
}
.topnav__account {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.6rem 0.35rem 0.35rem;
  border: 1px solid #26262b;
  background: #141418;
  color: var(--fg);
  border-radius: 999px;
  cursor: pointer;
  font: inherit;
}
.topnav__account:hover { background: #1a1a1f; }
.topnav__avatar {
  width: 26px; height: 26px;
  border-radius: 50%;
  background: #2a2a31;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex: 0 0 auto;
}
.topnav__avatar-img {
  width: 100%; height: 100%;
  object-fit: cover;
}
.topnav__avatar-initials {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--fg);
}
.topnav__account-name {
  font-size: 0.85rem;
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topnav__caret {
  width: 10px; height: 10px;
  color: var(--muted);
}
.topnav__menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 200px;
  background: #18181c;
  border: 1px solid #26262b;
  border-radius: 10px;
  padding: 0.25rem;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
}
.topnav__menu-item {
  display: block;
  padding: 0.5rem 0.65rem;
  color: var(--fg);
  text-decoration: none;
  border-radius: 6px;
  font-size: 0.88rem;
}
.topnav__menu-item:hover { background: #222228; }
@media (max-width: 720px) {
  .topnav__account-name { display: none; }
}

/* =========================================================================
   App shell (used by /, /profile, and /review/ scoped pages)
   ========================================================================= */
.app {
  /* 1540px ceiling matches the generic <main> rule so the projects
   * page + the player page stay the same width. ~8% wider than the
   * previous 1425 — gives the player a bit more room without sprawling. */
  max-width: 1540px;
  margin: 0 auto;
  padding: 1.5rem 2rem 3rem;
}
.app__header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
}
.app__title {
  font-size: 1.5rem;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.01em;
}
/* Workspace › Library › Project breadcrumb shown at the top of the
 * project library page and the player page. Each segment is a link
 * that navigates up the hierarchy. Subtle muted styling so it reads
 * as orientation chrome rather than primary content — the project
 * H1 below it stays the visual anchor. */
.breadcrumb {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex-wrap: wrap;
  margin: 0 0 0.5rem;
  font-size: 0.82rem;
  color: var(--muted);
  min-width: 0;
}
.breadcrumb__link {
  color: var(--muted);
  text-decoration: none;
  padding: 0.1rem 0.3rem;
  margin: -0.1rem -0.3rem;
  border-radius: 4px;
  transition: color 120ms ease, background 120ms ease;
}
.breadcrumb__link:hover {
  color: var(--fg);
  background: #18181c;
}
.breadcrumb__sep {
  color: var(--muted);
  opacity: 0.6;
  font-size: 0.85rem;
}
.breadcrumb__current {
  color: var(--fg);
  font-weight: 500;
}

/* Stacked column holding the H1 title + the library line below. Sits
 * to the right of the workspace logo when present; flexes to fill
 * the remaining horizontal space. min-width:0 lets long workspace
 * names ellipsis-truncate inside their span rather than push the
 * header off-screen. */
.app__title-stack {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}
.app__title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
/* Small chip/badge style label next to the workspace + library names.
 * Tiny uppercase pill on a soft background — visually distinct from
 * the name, so the size mismatch between H1 and badge doesn't read as
 * "messy" (it reads as a tag, like the OWNER / PERSONAL chips in the
 * workspace switcher rows). */
.app__title__type {
  font-size: 0.62rem;
  font-weight: 600;
  color: var(--muted);
  background: #1d2128;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  vertical-align: middle;
  position: relative;
  top: -0.15em;
}
/* Second line of the header: library name + "library" chip +
 * separator + project count (or empty-state copy). Same chip styling
 * as the workspace label up top — the two lines feel like a related
 * pair. */
.app__lib-line {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin: 0;
  font-size: 0.95rem;
  color: var(--muted);
  min-width: 0;
}
.app__lib-line__name {
  color: var(--fg);
  font-weight: 500;
}
.app__lib-line__type {
  font-size: 0.62rem;
  font-weight: 600;
  color: var(--muted);
  background: #1d2128;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.app__lib-line__sep {
  color: var(--muted);
  margin: 0 0.05rem;
}
/* Inline logo + title cluster. The logo sits to the left of the
 * heading block so a team workspace reads as "<logo> Studio Co. ·
 * Projects" without disturbing the rest of the header row's
 * layout. Hidden by default — JS shows it only when the active
 * org is a team org with a brand logo set. */
.app__title-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}
.app__title-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  flex: 0 0 auto;
  border-radius: 10px;
  background: #1d2a28;
  overflow: hidden;
}
.app__title-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.app__back {
  display: inline-block;
  margin-bottom: 0.3rem;
  color: var(--muted);
  font-size: 0.85rem;
  text-decoration: none;
}
.app__back:hover { color: var(--fg); }
.app__subtitle {
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0.25rem 0 0;
}
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.55rem 0.95rem;
  border: 0;
  border-radius: 8px;
  background: var(--accent);
  color: #18140a;
  font: inherit;
  font-weight: 600;
  font-size: 0.88rem;
  cursor: pointer;
  text-decoration: none;
}
.btn:hover { filter: brightness(1.05); }
.btn:disabled,
.btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  filter: none;
}
.btn:disabled:hover,
.btn[disabled]:hover { filter: none; background: inherit; }
.btn--ghost {
  background: transparent;
  border: 1px solid #2b2b30;
  color: var(--fg);
  font-weight: 500;
}
.btn--ghost:hover { background: #1a1a1f; }
.btn--ghost:disabled:hover,
.btn--ghost[disabled]:hover { background: transparent; }
.btn--danger {
  background: #b14646;
  color: #fff;
}

/* ---- Projects grid ----------------------------------------------------- */
.projects-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1rem;
}

/* Toolbar above the projects-grid (count + view toggle). Mirrors the
 * library page's toolbar so the projects index feels familiar — same
 * markup classes, same toggle UX. */
.library-toolbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
  padding: 0.4rem 0.6rem;
  background: #15151a;
  border: 1px solid #22222a;
  border-radius: 8px;
}
.library-toolbar__count {
  color: var(--muted);
  font-size: 0.85rem;
}
.library-toolbar__spacer { flex: 1 1 auto; }
.library-view-toggle {
  display: inline-flex;
  border: 1px solid #2b2b33;
  border-radius: 6px;
  overflow: hidden;
}
.library-view-btn {
  background: transparent;
  border: 0;
  color: var(--muted);
  font: inherit;
  font-size: 0.82rem;
  padding: 0.35rem 0.55rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.library-view-btn + .library-view-btn { border-left: 1px solid #2b2b33; }
.library-view-btn svg { fill: currentColor; }
.library-view-btn:hover { background: #1c1c24; color: var(--fg); }
.library-view-btn.is-active {
  background: #2a2218;
  color: var(--accent, #f4c76b);
}

/* Sort dropdown — compact "Sort: <select>" pair that lives in the
 * library toolbar next to the view toggle. Same visual language as the
 * view toggle so they read as a related cluster. Used on the projects
 * landing page and the per-project library page. */
.sort-control {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.sort-control__label {
  color: var(--muted);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.sort-control__select {
  background: #0e0e10;
  color: var(--fg);
  border: 1px solid #2b2b33;
  border-radius: 6px;
  padding: 0.3rem 0.55rem;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 14px) 50%, calc(100% - 9px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 1.6rem;
}
.sort-control__select:hover { border-color: #46506a; }
.sort-control__select:focus-visible { outline: none; border-color: var(--accent, #f4c76b); }

/* List-view rendering of the projects page. One row per project,
 * tabular columns. The same .project-card class is reused so all
 * existing menu wiring works — the .project-card--row modifier swaps
 * the card layout from grid/block to a flex row. */
.projects-list {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.projects-list__head {
  display: grid;
  /* Adds a thumb-strip column between role + date so the header lines
   * up with the row body. The strip has no header text. */
  grid-template-columns: minmax(180px, 2fr) minmax(0, 3fr) 90px minmax(0, 1.4fr) 110px 36px;
  gap: 0.5rem;
  padding: 0.4rem 0.75rem;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  border-bottom: 1px solid #22222a;
  margin-bottom: 0.2rem;
}
.project-card--row {
  display: grid;
  grid-template-columns: 1fr 36px;
  align-items: center;
  /* Halved row height: row body now uses 0.3rem block padding instead
   * of 0.55rem. Also drops the link's font-size a touch so the row
   * reads as compact rather than crammed. */
  min-height: 0;
  padding: 0;
  position: relative;
}
.project-card--row .project-card__link {
  display: grid;
  grid-template-columns: minmax(180px, 2fr) minmax(0, 3fr) 90px minmax(0, 1.4fr) 110px;
  gap: 0.5rem;
  padding: 0.3rem 0.75rem;
  align-items: center;
  text-decoration: none;
  color: var(--fg);
  border-radius: 8px;
  font-size: 0.85rem;
}
.project-card--row .project-card__link:hover { background: #1c1c24; }
.project-card--row .project-card__name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 0;
  font-size: 0.95rem;
}
.project-card--row .project-card__desc {
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 0;
  font-size: 0.85rem;
}
.project-card--row .project-card__role {
  color: var(--muted);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.project-card--row .project-card__date {
  color: var(--muted);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}

/* "Updates" column on each project list row. Renders one pill per
 * activity kind ("2 new comments", "1 status update", "1 new
 * version") so the cell reads as a tiny changelog. Empty cells get
 * a muted em-dash so a quiet project doesn't look like a broken
 * layout. */
.project-card__updates {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  min-width: 0;
}
.project-card__updates--empty {
  color: var(--muted);
  font-size: 0.85rem;
}
.project-card__updates__pill {
  display: inline-flex;
  align-items: center;
  font-size: 0.78rem;
  font-weight: 500;
  line-height: 1.4;
  padding: 0.18rem 0.6rem;
  border-radius: 999px;
  background: rgba(244, 199, 107, 0.16);
  color: var(--accent);
  border: 1px solid rgba(244, 199, 107, 0.45);
  white-space: nowrap;
}
.project-card--row .project-card__row-actions {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.project-card--row .project-card__menu-btn {
  position: static;     /* override grid-card absolute positioning */
}
@media (max-width: 720px) {
  /* Drop the description + role + thumb-strip columns on narrow
   * screens so the row still reads at small widths. */
  .projects-list__head { grid-template-columns: minmax(120px, 2fr) 110px 36px; }
  .projects-list__head > :nth-child(2),
  .projects-list__head > :nth-child(3),
  .projects-list__head > :nth-child(4) { display: none; }
  .project-card--row .project-card__link {
    grid-template-columns: minmax(120px, 2fr) 110px;
  }
  .project-card--row .project-card__desc,
  .project-card--row .project-card__role,
  .project-card--row .project-card__updates { display: none; }
}
.project-card {
  background: #15151a;
  border: 1px solid #22222a;
  border-radius: 12px;
  color: var(--fg);
  transition: border-color 0.15s, transform 0.1s, box-shadow 0.15s, background 0.15s;
  /* Cards host an absolute-positioned overflow menu; establish a positioning
     context so the ⋯ button and its dropdown stay inside the card. */
  position: relative;
  overflow: hidden;
}
.project-card:hover {
  border-color: #4a525d;
  background: #1c1f23;
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(0,0,0,0.4);
}
/* Clickable body of the card: fills the whole card so the usual "click
   anywhere to open" affordance still works. The overflow button sits on
   top with a higher z-index. */
.project-card__link {
  display: flex;
  flex-direction: column;
  color: inherit;
  text-decoration: none;
}
/* Body padding is on the inner __body now — the thumbnail strip sits
 * edge-to-edge above it. */
.project-card__body {
  padding: 0.85rem 1rem 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-height: 90px;
}

/* ---- Project thumbnail panel ----------------------------------------
 * Same dark-gradient + film-grain treatment as the library's clip cards.
 * Reads as "this card represents a media container" — a project is
 * conceptually a folder of clips so the visual language matches.
 *
 * On populated projects, the renderer also injects a
 * .project-card__montage element holding up to 4 of the project's most
 * recent clip thumbnails. The montage sits BEHIND the radial-light
 * pseudo + grain pseudo so the existing styling still tints it
 * (mix-blend-mode: screen on the radial paints colour over the
 * blurred content, the grain pass on top textures everything). */
.project-card__thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background-color: #0a0c0e;
  background-image:
    var(--film-grain),
    linear-gradient(180deg, #16191d 0%, #0a0c0e 100%);
  background-size: 60px 60px, auto;
  background-blend-mode: overlay, normal;
}

/* ---- Project content montage (Option B: blurred contact sheet) ------
 * Up to 4 thumbnails laid out as a CSS grid covering the tile, then
 * heavily blurred + saturated. The blur is large enough to abstract
 * away individual subjects while preserving each clip's broad colour
 * shape, which is exactly what we want — the tile feels like THAT
 * project without spoiling the content.
 *
 * Layout per count:
 *   1 → single full-bleed image
 *   2 → side-by-side halves
 *   3 → top spans both columns, bottom split into two
 *   4 → 2×2 grid
 * Empty projects render no .project-card__montage at all so the
 * pure film-stock backdrop carries the tile.
 *
 * z-index 0 puts the montage above the .project-card__thumb's
 * background but BELOW the ::before radial light (which has no
 * z-index but paints late in source order with mix-blend-mode:
 * screen, so it tints the blurred frames) and the ::after grain
 * pass (z-index: 1). The corner badge (z-index: 2) stays on top. */
.project-card__montage {
  position: absolute;
  inset: 0;
  display: grid;
  z-index: 0;
  pointer-events: none;
}
.project-card__montage img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Blur masks recognisable subject; saturate keeps the blurred
   * regions from looking washed out. Scale + transform hides the
   * blur edges that would otherwise show as light borders inside
   * each grid cell. (Reduced 25% from the original 20px so the
   * underlying colour shapes are a touch more legible.) */
  filter: blur(15px) saturate(1.25);
  transform: scale(1.18);
  /* Slight darken so the radial-light pseudo + corner badge stay
   * readable even when a clip's hero frame is bright. */
  opacity: 0.85;
}
.project-card__montage--1 {
  grid-template: "a" 1fr / 1fr;
}
.project-card__montage--2 {
  grid-template: "a b" / 1fr 1fr;
}
.project-card__montage--3 {
  grid-template:
    "a a" 1fr
    "b c" 1fr
    / 1fr 1fr;
}
.project-card__montage--4 {
  grid-template:
    "a b" 1fr
    "c d" 1fr
    / 1fr 1fr;
}
/* Per-kind teal/sage radial wash. Sits ABOVE the montage with a
 * straightforward 50 % opacity (instead of the previous
 * mix-blend-mode: screen, which only ever added light) so the
 * green-leaning vuwa accent reads through clearly even when the
 * clip frames behind are bright or saturated. z-index 1 keeps it
 * above the montage (z 0) and below the grain pass (also z 1, but
 * declared later in source order so it wins for the texture). */
.project-card__thumb[data-kind="project"]::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 80% at 30% 70%, #1d4047 0%, transparent 55%),
    radial-gradient(100% 80% at 80% 20%, #2a4338 0%, transparent 60%);
  opacity: 0.5;
  z-index: 1;
  pointer-events: none;
}
/* Light second grain pass on top so texture reads consistently. */
.project-card__thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--film-grain);
  background-size: 80px 80px;
  opacity: 0.18;
  mix-blend-mode: overlay;
  pointer-events: none;
  z-index: 1;
}

/* ---- Project kind-icon (bottom-left corner badge) -------------------
 * Same film-stock chip the clip cards carry, just with a "stack /
 * project" glyph. Sized 28×28 with the corner-cuts pseudo for the
 * tiny film-roll edge detail. Tinted teal so it sits in the same
 * cool palette family. */
.project-card__kind-icon {
  position: absolute;
  left: 8px;
  bottom: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 5px;
  background-color: var(--film-bg-audio);
  background-image:
    radial-gradient(120% 120% at 30% 0%, rgba(255,255,255,0.10) 0%, transparent 50%),
    radial-gradient(120% 120% at 70% 100%, rgba(0,0,0,0.30) 0%, transparent 55%),
    var(--film-grain);
  background-size: auto, auto, 60px 60px;
  z-index: 2;
  pointer-events: none;
  /* Cyan glyph — same hue the vuwa wordmark's play triangle uses,
   * so the project corner badge reads as a tiny vuwa lockup. */
  color: #5fc8d1;
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,0.06),
    inset 0 1px 0 rgba(255,255,255,0.05),
    0 1px 3px rgba(0,0,0,0.55);
}
.project-card__kind-icon::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background:
    linear-gradient(90deg, rgba(255,255,255,0.07) 1px, transparent 1px) top left / 6px 1px no-repeat,
    linear-gradient(90deg, rgba(255,255,255,0.07) 1px, transparent 1px) top right / 6px 1px no-repeat,
    linear-gradient(90deg, rgba(0,0,0,0.45) 1px, transparent 1px) bottom left / 6px 1px no-repeat,
    linear-gradient(90deg, rgba(0,0,0,0.45) 1px, transparent 1px) bottom right / 6px 1px no-repeat;
}
.project-card__kind-icon svg {
  display: block;
  width: 14px;
  height: 14px;
  position: relative;
  z-index: 1;
}
/* Folder-style project: in case we ever add nested project archives. */
.project-card__kind-icon[data-kind="folder"] {
  background-color: var(--film-bg-doc);
  color: var(--sage);
}

/* List view (--row variant) is dense by default: rows about a third
 * the height of the grid-view card. Defined AFTER the base
 * .project-card / .project-card__link rules so the source-order
 * tie-break makes these win — without that, the base rules above
 * pin every row to 140px tall. .project-card.project-card--row
 * picks up specificity 0,2,0 to also beat any future identical-
 * weight rule that might land later. */
.project-card.project-card--row {
  min-height: 0;
  /* Each row reads as its own card: subtle dark fill, hairline
   * border, gentle rounding. The .projects-list parent already adds
   * a 0.25rem gap between rows so the borders read as separators
   * rather than merging into a continuous line. */
  background: #15151a;
  border: 1px solid #22222a;
  border-radius: 8px;
}
.project-card.project-card--row:hover {
  border-color: #3a3a44;
  background: #1a1a20;
}
.project-card--row .project-card__link {
  min-height: 0;
  /* Slightly generous vertical padding so rows breathe — anything
   * tighter than ~0.6rem packs the names in too close to read at a
   * glance. Horizontal padding stays the same so the columns line
   * up with the .projects-list__head row above. */
  padding: 0.65rem 0.75rem;
  font-size: 0.85rem;
}
.project-card--row .project-card__name { font-size: 0.88rem; }
.project-card--row .project-card__desc { font-size: 0.8rem;  }
/* Shrink the decorative thumbnail strip so it doesn't drive row
 * height past the text. Empty strips already collapse via :empty. */
.project-card--row .project-card__thumb-strip { height: 18px; }
.project-card__menu-btn {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  background: transparent;
  border: 0;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
}
.project-card__menu-btn:hover,
.project-card__menu-btn[aria-expanded="true"] {
  background: #22222a;
  color: var(--fg);
}
.project-card__menu {
  position: absolute;
  top: 2.2rem;
  right: 0.5rem;
  background: #17171c;
  border: 1px solid #2a2a30;
  border-radius: 8px;
  padding: 0.25rem;
  min-width: 180px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
  z-index: 3;
  display: flex;
  flex-direction: column;
}
.project-card__menu button {
  background: transparent;
  border: 0;
  color: var(--fg);
  padding: 0.5rem 0.6rem;
  text-align: left;
  border-radius: 6px;
  font: inherit;
  cursor: pointer;
}
.project-card__menu button:hover { background: #22222a; }
.project-card__menu button[data-action="delete"] { color: #ff8a8a; }
.project-card__menu button[data-action="delete"]:hover { background: #33181a; }
.project-card__name {
  font-size: 1.05rem;
  font-weight: 600;
  margin: 0 0 0.25rem;
}
.project-card__desc {
  color: var(--muted);
  font-size: 0.85rem;
  margin: 0 0 auto;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.project-card__meta {
  margin-top: 0.75rem;
  display: flex;
  gap: 0.75rem;
  color: var(--muted);
  font-size: 0.78rem;
}
.project-card__role {
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.empty-state {
  border: 1px dashed #2b2b32;
  border-radius: 12px;
  padding: 2.5rem 1.5rem;
  text-align: center;
  color: var(--muted);
}
.empty-state h2 {
  color: var(--fg);
  font-size: 1.1rem;
  margin: 0 0 0.5rem;
}
.empty-state p { margin: 0 0 1rem; }

/* ---- Modal / dialog ---------------------------------------------------- */
.modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}
.modal {
  background: #17171c;
  border: 1px solid #26262b;
  border-radius: 12px;
  width: min(460px, 92vw);
  padding: 1.25rem;
}
.modal h2 {
  margin: 0 0 0.9rem;
  font-size: 1.05rem;
  font-weight: 600;
}
.modal .field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-bottom: 0.75rem;
}
.modal label {
  font-size: 0.8rem;
  color: var(--muted);
}
.modal input[type="text"],
.modal input[type="email"],
.modal input[type="password"],
.modal textarea {
  background: #0e0e11;
  border: 1px solid #2a2a30;
  border-radius: 8px;
  color: var(--fg);
  padding: 0.6rem 0.7rem;
  font: inherit;
  font-size: 0.9rem;
}
.modal textarea { min-height: 80px; resize: vertical; }

/* Document preview modal — used by the library page to render PDFs
 * and text files inline instead of routing through the full player.
 * Wider than the default modal (a PDF needs the room) and the body
 * fills the available height. */
.modal--doc-preview {
  width: min(960px, 92vw);
  height: min(85vh, 800px);
  display: flex;
  flex-direction: column;
  padding: 0.85rem 0.85rem 0.85rem;
}
.modal--doc-preview .modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.modal--doc-preview .doc-preview__title {
  font-size: 1rem;
  font-weight: 600;
  margin: 0;
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.modal--doc-preview .doc-preview__actions {
  display: flex;
  gap: 0.4rem;
}
.modal--doc-preview .doc-preview__body {
  flex: 1;
  min-height: 0;
  position: relative;
  background: #0b0b0e;
  border: 1px solid #2a2a30;
  border-radius: 6px;
  overflow: hidden;
}
.modal--doc-preview iframe {
  width: 100%;
  height: 100%;
  border: 0;
  background: #fff;
}
.modal--doc-preview .doc-preview__text {
  margin: 0;
  width: 100%;
  height: 100%;
  padding: 1rem 1.2rem;
  overflow: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  color: #e5e7eb;
  background: #0b0b0e;
}

/* ---------- Native <select> dropdowns ----------------------------------
 * Browser default selects render as bright white boxes that look pasted-
 * on against the dark UI. Skin every <select> on the site to match the
 * inputs above: dark fill, subtle border, custom chevron, focus ring.
 * Existing custom select classes (.sort-control__select, .target-select,
 * .lut-select, .version-row__select) keep their layout-specific tweaks
 * but inherit this base. */
select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-color: #0e0e11;
  color: var(--fg);
  border: 1px solid #2a2a30;
  border-radius: 6px;
  padding: 0.45rem 1.9rem 0.45rem 0.65rem;
  font: inherit;
  font-size: 0.88rem;
  line-height: 1.2;
  cursor: pointer;
  /* Custom chevron drawn with two crossed gradients — keeps the rule
   * SVG-free + scales with the text colour. */
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 14px) 50%, calc(100% - 9px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  transition: border-color 120ms ease, background-color 120ms ease;
}
select:hover { border-color: #46506a; }
select:focus-visible {
  outline: none;
  border-color: var(--accent, #f4c76b);
  box-shadow: 0 0 0 3px rgba(244, 199, 107, 0.18);
}
select:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
/* The dropdown list itself can't be fully restyled cross-browser, but we
 * can hint to the OS at least the colour scheme so option items don't
 * render as bright white. Chrome + Firefox honour `color-scheme`. */
select option {
  background-color: #15151a;
  color: var(--fg);
}
/* Selects inside modals + nav surfaces inherit the same base; provide a
 * slightly narrower variant where space is tight. */
.topnav select,
.share-toolbar select,
.modal select { padding-top: 0.4rem; padding-bottom: 0.4rem; }
.modal__actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.modal__error {
  color: #ff7070;
  font-size: 0.82rem;
  margin: 0.25rem 0 0;
  min-height: 1em;
}

/* ---- Profile ----------------------------------------------------------- */
.profile {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 2rem;
}
.profile__sections {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.profile__tab {
  text-align: left;
  background: transparent;
  border: 0;
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 0.9rem;
}
.profile__tab:hover { color: var(--fg); background: #17171b; }
.profile__tab.is-active { color: var(--fg); background: #1d1d22; }
.profile__panel {
  background: #15151a;
  border: 1px solid #22222a;
  border-radius: 12px;
  padding: 1.75rem 1.85rem 1.85rem;
}
.profile__panel h2 {
  margin: 0 0 0.3rem;
  font-size: 1.1rem;
  font-weight: 500;
  letter-spacing: -0.005em;
}
.profile__panel p.hint {
  color: var(--muted);
  font-size: 0.85rem;
  margin: 0 0 1.25rem;
  text-align: left;
}

/* Base .field rule used by the profile + project-settings forms.
 * Stacks label above input with a small gap and consistent
 * bottom-margin between siblings. The .modal .field rule above
 * still applies inside modals (and tightens margin); this is
 * the page-level baseline. */
.field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-bottom: 0.9rem;
}
/* HTML `hidden` attribute needs to win over the `display: flex`
 * above — otherwise a JS-toggled .field stays visible because the
 * browser's default UA rule loses the specificity battle. */
.field label {
  font-size: 0.82rem;
  color: var(--muted);
}
.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field input[type="url"],
.field input[type="search"],
.field input[type="number"],
.field select,
.field textarea {
  background: #0e0e11;
  border: 1px solid #2a2a30;
  border-radius: 8px;
  color: var(--fg);
  padding: 0.55rem 0.7rem;
  font: inherit;
  font-size: 0.9rem;
  width: 100%;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--accent);
}
.field input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* ---- Account form layout -----------------------------------------
 * Section headings + a 2-column grid of fields, used by the Account
 * panel to keep the new "Identity / Work / About" blocks readable
 * without towering down the page. */
.field-group__head {
  margin: 1.5rem 0 0.6rem;
  font-size: 0.78rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding-bottom: 0.45rem;
  border-bottom: 1px solid #22222a;
}
.field-group__head:first-of-type { margin-top: 0.5rem; }

/* Settings section card — wraps each top-level area inside a profile
 * tab (Brand kit, Members, vuwa.io subdomain, Custom domains, Enterprise
 * SSO, etc.) in a visually-contained block. Subtle inset card style so
 * it reads as "a section within the tab panel" rather than competing
 * with the panel's own background.
 *
 * Title + hint stack vertically as plain block elements (no flex) so
 * they land flush-left underneath each other instead of side-by-side
 * with a drifting gap — the flex layout we tried first looked
 * deceptively reasonable in CSS and terrible on the page. */
.settings-section {
  /* Bg sits a noticeable step darker than the parent .profile__panel
   * (#15151a) so each card reads as a recessed "well" rather than the
   * same shade with just a line around it. */
  background: #0a0c11;
  border: 1px solid #262d3a;
  border-radius: 10px;
  padding: 1.35rem 1.5rem 1.5rem;
  margin-bottom: 1.1rem;
}
.settings-section:last-child  { margin-bottom: 0; }
/* We use a <header> element for the section head row so the title +
 * hint group is semantically a header. There's a global rule for the
 * landing-page hero — `header { padding: 2.5rem 1.5rem 1.25rem;
 * text-align: center; max-width: 900px }` — that bleeds into every
 * <header> on the page if we don't reset. Without this, the title
 * lands ~24px more indented than the inputs below it (the 1.5rem
 * inherited padding) and reads as centered. Reset belt-and-braces so
 * future tweaks to the landing-page header can't break this card. */
.settings-section,
.settings-section__head {
  text-align: left;
}
.settings-section__head {
  /* Generous gap before the first content row inside the card +
   * a hairline rule below the head so the header reads as a clear
   * cap on the section rather than just text floating above content. */
  margin: 0 0 1.25rem;
  padding: 0 0 1rem;
  max-width: none;
  border-bottom: 1px solid #1a1f2a;
}
.settings-section__title {
  margin: 0 0 0.3rem;
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: #e8e8ee;
}
.settings-section__hint {
  margin: 0;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.5;
}
/* Per-field rhythm inside a section. We allow more space than a tight
 * form because the card already provides containment — these fields
 * should breathe. */
.settings-section .field { margin-bottom: 1.15rem; }
.settings-section .field:last-child { margin-bottom: 0; }
.settings-section .field-grid { margin-bottom: 1.2rem; }
/* Action button row (Save / Submit) wants real separation from the
 * field block above — feels like a "commit" beat. 2rem clears the
 * collapsed margin from the trailing field and leaves visible
 * breathing room around the button itself.
 *
 * Also align bottom-left, not bottom-right. The global .modal__actions
 * rule above sets justify-content: flex-end, which makes sense inside
 * an actual <dialog>-style modal (primary action at the corner) but
 * looks adrift inside a wide profile-page card. Override for both
 * .settings-section and any form inside .profile__panel so Account,
 * Language, and Login-credentials all match. */
.settings-section .modal__actions,
.settings-section .field-actions,
.profile__panel form .modal__actions,
.profile__panel form .field-actions {
  margin-top: 2rem;
  justify-content: flex-start;
}
/* Sub-headings inside a section (e.g. "WorkOS link" / "Claimed email
 * domains" inside the SSO card) — visible divider above + a real gap
 * so two logical halves of a section don't crash into each other. */
.settings-section .field-group__head {
  margin: 1.6rem 0 0.7rem;
  border-bottom: 0;
  padding: 1rem 0 0;
  border-top: 1px solid #1a1f2a;
  font-size: 0.82rem;
  text-transform: none;
  letter-spacing: 0;
  color: #cdd2dc;
  font-weight: 500;
}
.settings-section .field-group__head:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* Input + select width cap. Wide-screen panels were stretching every
 * text input across the full ~900px card, which made a single email
 * field look like a banner. Cap at 28em (~448px) — comfortably wider
 * than the longest realistic value (an org id, a hostname, a verified
 * email) but narrow enough that the card breathes. Color pickers and
 * file inputs are intentionally small + don't need a cap; textareas
 * benefit from the full width for multi-line content.
 *
 * Scoped to the whole .profile__panel (not just .settings-section) so
 * the Language / Notifications tabs — which don't wrap in section
 * cards — still benefit. */
.profile__panel input[type="text"],
.profile__panel input[type="email"],
.profile__panel input[type="url"],
.profile__panel input[type="password"],
.profile__panel input[type="search"],
.profile__panel input[type="tel"],
.profile__panel input[type="number"],
.profile__panel select {
  max-width: 28em;
}
.profile__panel textarea { max-width: 36em; }
/* Action rows (input + button) — when the input has flex:1 1 auto, the
 * cap above stops the input growing wider than 28em. The button sits
 * next to it and the remaining row is allowed to stay empty. Pin the
 * form's own max-width too so the right edge of the action row doesn't
 * float halfway across the card. */
.profile__panel form { max-width: 32em; }
/* Brand-kit field-grid is the one form-row exception: three controls
 * (display name + two colour swatches) belong on a single row, so let
 * it use the natural 1fr 1fr feel but still cap the wrapper. */
.settings-section .field-grid { max-width: 36em; }
/* Bare "field" wrapper (label + input) — same cap as a form so the
 * Logo row, the Display name field, and the colour pickers all live
 * inside the same visual lane rather than each picking its own width. */
.settings-section > .field,
.settings-section .field { max-width: 36em; }
/* Member list rows live in their own visual lane — cap to match the
 * forms so the org-members list doesn't stretch edge-to-edge while
 * the invite form below it tucks in at 32em. */
.settings-section .orgs__members,
.profile__panel .orgs__members,
.settings-section #org-domains,
.settings-section #sso-domains-list {
  max-width: 32em;
}

.field-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.85rem 1rem;
  margin-bottom: 0.5rem;
}
.field-grid .field { margin-bottom: 0; }
.field-grid .field--full { grid-column: 1 / -1; }
@media (max-width: 640px) {
  .field-grid { grid-template-columns: 1fr; }
}

/* Tighten the existing .field rule's bottom-margin inside the
 * profile panel so the grid + section-headings drive the rhythm
 * rather than per-field padding. */
.profile__panel .field { margin-bottom: 1.1rem; }
.profile__panel .field:last-child { margin-bottom: 0; }
.profile__panel .field textarea {
  background: #0e0e11;
  border: 1px solid #2a2a30;
  border-radius: 8px;
  color: var(--fg);
  padding: 0.6rem 0.7rem;
  font: inherit;
  font-size: 0.9rem;
  resize: vertical;
  width: 100%;
}
.profile__panel .field textarea:focus {
  outline: none;
  border-color: var(--accent);
}
/* Form-level hints that follow an input render left-aligned + tighter
 * than the panel-level intro hint. Override the global .hint center
 * alignment which only suits standalone marketing copy. */
.profile__panel .field .hint {
  text-align: left;
  margin: 0.4rem 0 0;
  font-size: 0.78rem;
}
.profile__row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem 0;
  border-top: 1px solid #22222a;
  gap: 1rem;
}
.profile__row:first-of-type { border-top: 0; }
.profile__row-main {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}
.profile__row-main .topnav__avatar { width: 34px; height: 34px; }
.profile__row-sub {
  color: var(--muted);
  font-size: 0.82rem;
}
.profile__notice {
  background: rgba(244, 199, 107, 0.08);
  border: 1px solid rgba(244, 199, 107, 0.25);
  color: var(--fg);
  padding: 0.55rem 0.75rem;
  border-radius: 8px;
  font-size: 0.85rem;
  margin-bottom: 0.75rem;
  min-height: 1.3em;
}
.profile__notice:empty { display: none; }
.profile__notice.is-error {
  background: rgba(255, 112, 112, 0.08);
  border-color: rgba(255, 112, 112, 0.3);
}

/* ---- Tables inside profile panels (e.g. API tokens list) ------------- */
.profile__table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
  margin: 0.5rem 0 1.25rem;
}
.profile__table th,
.profile__table td {
  text-align: left;
  padding: 0.55rem 0.6rem;
  border-bottom: 1px solid #232a36;
  vertical-align: middle;
}
.profile__table th {
  font-weight: 600;
  color: #a4a9b3;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-bottom-color: #2a2f3a;
}
.profile__table code {
  background: #14171c;
  border: 1px solid #232a36;
  border-radius: 4px;
  padding: 1px 6px;
  font-size: 0.82rem;
  color: #b4bac3;
}

/* ---- Token reveal modal — copy-row + "save this now" callout --------- */
.modal__label {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: 0.5rem 0 0.75rem;
  font-size: 0.86rem;
  color: #b4bac3;
}
.modal__label input[type="text"],
.modal__label input[type="password"] {
  width: 100%;
  background: #0a0a0c;
  color: #e8edf5;
  border: 1px solid #232a36;
  border-radius: 6px;
  padding: 0.55rem 0.7rem;
  font: inherit;
  box-sizing: border-box;
}
.modal__label input[type="text"]:focus { outline: none; border-color: #5fc8d1; }
.modal__copyrow {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
}
.modal__copyrow input {
  flex: 1 1 auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.82rem;
}
.modal__strong-notice {
  background: rgba(244, 199, 107, 0.12);
  border: 1px solid rgba(244, 199, 107, 0.35);
  color: #f4c76b;
  padding: 0.65rem 0.8rem;
  border-radius: 8px;
  font-size: 0.86rem;
  font-weight: 600;
  margin: 0 0 0.75rem;
}

@media (max-width: 820px) {
  .profile { grid-template-columns: 1fr; }
  .profile__sections { flex-direction: row; overflow-x: auto; }
}

/* ---- Members + invites panel (inside /projects/:id/) ----------------- */
.members-list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.member-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.55rem 0.65rem;
  border-radius: 8px;
  background: #141418;
}
.member-row__main { flex: 1 1 auto; min-width: 0; }
.member-row__name {
  font-size: 0.9rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.member-row__email {
  color: var(--muted);
  font-size: 0.78rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.member-row__role {
  color: var(--muted);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* ---- Share modal (specialised .modal variant) ------------------------- */
/* The share modal is wider than the default .modal because it hosts a
   two-tab layout with form + result card, and the rendered share URL
   needs horizontal room so it doesn't wrap awkwardly mid-slug. */
.modal--share {
  width: min(640px, 94vw);
  padding: 1.25rem 1.5rem 1.5rem;
}
.modal__head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}
.modal__head h2 { margin: 0; flex: 1; }
.modal--share .profile__sections {
  flex-direction: row;
  margin: 0 0 1rem;
  gap: 0.25rem;
}
.modal--share .profile__panel {
  background: transparent;
  border: 0;
  padding: 0;
}

/* Two-step form inside the "Send a link" tab. */
.share-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.share-form__lede {
  color: var(--muted);
  font-size: 0.88rem;
  margin: 0 0 0.25rem;
}
.share-form__more {
  background: #101015;
  border: 1px solid #22222a;
  border-radius: 10px;
  padding: 0.5rem 0.9rem;
}
.share-form__more > summary {
  cursor: pointer;
  color: #a0b4ff;
  font-size: 0.88rem;
  padding: 0.4rem 0;
  list-style: none;
}
.share-form__more > summary::-webkit-details-marker { display: none; }
.share-form__more > summary::before {
  content: "▸ ";
  display: inline-block;
  transition: transform 0.15s ease;
  color: var(--muted);
  font-size: 0.8em;
}
.share-form__more[open] > summary::before { transform: rotate(90deg); }
.share-form__more[open] > summary { margin-bottom: 0.5rem; }

/* Nested LUT-options disclosure inside the Advanced options block.
 * Same chevron + colour treatment as the parent details, but with a
 * lighter background so it reads as a sub-section rather than a
 * peer-level panel. */
.share-form__lut-wrap {
  background: #161620;
  border: 1px solid #262633;
  border-radius: 8px;
  padding: 0.45rem 0.8rem;
  margin: 0.4rem 0 0.6rem;
}
.share-form__lut-wrap > summary {
  cursor: pointer;
  color: #c5c8d1;
  font-size: 0.86rem;
  padding: 0.3rem 0;
  list-style: none;
}
.share-form__lut-wrap > summary::-webkit-details-marker { display: none; }
.share-form__lut-wrap > summary::before {
  content: "▸ ";
  display: inline-block;
  transition: transform 0.15s ease;
  color: var(--muted);
  font-size: 0.8em;
}
.share-form__lut-wrap[open] > summary::before { transform: rotate(90deg); }
.share-form__lut-wrap[open] > summary { margin-bottom: 0.35rem; }

/* Padding under the Create share link button so it doesn't run into
 * the next visual border (the result panel's top edge or the modal
 * frame). */
.modal__actions--share-create {
  padding-bottom: 0.6rem;
  margin-top: 0.85rem;
}

/* Inline hint pill next to a label (e.g. "Password  [optional]"). */
.field__hint-tag {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  background: #1d1d22;
  border: 1px solid #2a2a30;
  border-radius: 999px;
  padding: 0.08rem 0.45rem;
  margin-left: 0.4rem;
  vertical-align: middle;
}

/* A checkbox field uses a single-row flex so the label sits next to it. */
.modal .field--check {
  flex-direction: row;
  align-items: center;
  gap: 0.5rem;
}
.modal .field--check input[type="checkbox"] {
  width: auto;
  margin: 0;
}
.modal .field--check label {
  margin: 0;
  font-size: 0.88rem;
  color: var(--fg);
}

/* Password input + Suggest button on one row. */
.share-password-row {
  display: flex;
  gap: 0.4rem;
}
.share-password-row input {
  flex: 1 1 auto;
  min-width: 0;
}
.share-password-row .btn {
  flex: 0 0 auto;
}

/* The "your link is ready" result card replaces the form after a successful
   create. Big URL and a big copy button so the user has one obvious next
   move: paste it into Slack / email. */
.share-result {
  background: #101015;
  border: 1px solid #2a3350;
  border-radius: 12px;
  padding: 1rem 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.share-result__head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.share-result__kicker {
  color: #9ad3a5;
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}
.share-result__clip {
  color: var(--fg);
  font-weight: 500;
  font-size: 0.95rem;
  /* VFX filenames are routinely 80+ chars with no spaces (e.g.
     Dyson_285_Social_Round1_Shot0030_..._GradeV001_V2_TI9). Without
     this they overflow the modal and break the layout — let the name
     wrap freely so the whole thing stays inside the share panel. */
  min-width: 0;
  flex: 1 1 100%;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.share-result__url-row {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
  flex-wrap: wrap;
}
.share-result__url {
  flex: 1 1 auto;
  min-width: 0;
  background: #0b0b0e;
  border: 1px solid #2a2a30;
  border-radius: 8px;
  padding: 0.6rem 0.75rem;
  font: 0.86rem/1.3 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--fg);
  word-break: break-all;
  user-select: all;
}
.share-result__meta {
  color: var(--muted);
  font-size: 0.82rem;
}
.share-result__actions {
  display: flex;
  justify-content: flex-end;
}

.share-links-heading {
  margin: 1rem 0 0.25rem;
  font-size: 0.95rem;
  font-weight: 600;
}
/* An empty "Active links" list shouldn't leave blank space. */
#share-links-list:empty { margin: 0; }
.share-link-url {
  font: 0.78rem/1.3 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #a0b4ff;
}

/* ---------- Top-nav uploads tray ---------------------------------------- */
.topnav__tray-btn,
.topnav__bell-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  background: transparent;
  border: 0;
  color: #c5c8d1;
  padding: 0.3rem 0.45rem;
  border-radius: 6px;
  cursor: pointer;
}
.topnav__tray-btn:hover,
.topnav__bell-btn:hover { background: #1c1c22; color: #fff; }
.topnav__tray-count,
.topnav__bell-badge {
  background: var(--accent);
  color: #0a0a0e;
  font-size: 0.7rem;
  font-weight: 700;
  border-radius: 999px;
  padding: 1px 6px;
  line-height: 1.4;
  min-width: 18px;
  text-align: center;
}
.topnav__bell-badge {
  position: absolute;
  top: 0;
  right: 0;
  transform: translate(30%, -30%);
  background: #ff5b5b;
  color: #fff;
}
.topnav__tray,
.topnav__notif {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 0.25rem;
  min-width: 320px;
  max-width: 400px;
  background: #15151a;
  border: 1px solid #2b2b33;
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
  z-index: 200;
  padding: 0.4rem;
}
/* Row is a flex container holding a link (which fills the row) and a
 * dismiss × button as a sibling. The link itself is the grid that lays
 * out name / status / progress bar; nesting buttons inside anchors is
 * invalid HTML so the × has to live outside the <a>. */
.topnav__tray__row {
  display: flex;
  align-items: stretch;
  border-radius: 6px;
}
.topnav__tray__row:hover { background: #1c1c24; }
.topnav__tray__row__main {
  flex: 1;
  min-width: 0;                 /* let ellipsis actually clip */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "name status" "bar  bar";
  gap: 0.25rem 0.5rem;
  padding: 0.45rem 0.55rem;
  border-radius: 6px;
  text-decoration: none;
  color: #e8e8ee;
  font-size: 0.85rem;
}
.topnav__tray__row__dismiss {
  background: transparent;
  border: 0;
  color: #6b727d;
  font-size: 1.05rem;
  line-height: 1;
  padding: 0 0.55rem;
  margin: 0.2rem 0.1rem 0.2rem 0;
  cursor: pointer;
  border-radius: 6px;
  opacity: 0.65;
  transition: opacity 120ms ease, background 120ms ease, color 120ms ease;
}
.topnav__tray__row:hover .topnav__tray__row__dismiss { opacity: 1; }
.topnav__tray__row__dismiss:hover {
  background: #2a2a32;
  color: #fff;
}
.topnav__tray__row__dismiss:disabled { opacity: 0.4; cursor: default; }
.topnav__tray__name   { grid-area: name; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 500; }
.topnav__tray__status { grid-area: status; color: #8c94a0; font-size: 0.78rem; }
.topnav__tray__bar    {
  grid-area: bar;
  height: 4px;
  background: #2b2b33;
  border-radius: 2px;
  overflow: hidden;
}
.topnav__tray__bar > span {
  display: block;
  height: 100%;
  background: var(--accent);
  transition: width 0.25s ease-out;
}

/* Indeterminate bar — a sliding accent stripe used during phases
 * where we don't have a real percent (waiting for the worker to
 * cold-start, downloading, generating thumb/sprite, saving). The
 * inner span gets a fixed 35% width and is translated across the
 * track by the animation; the user sees a continuous left→right
 * shimmer instead of a frozen 100% from the upload phase. */
@keyframes topnav-tray-bar-shimmer {
  0%   { transform: translateX(-110%); }
  100% { transform: translateX(310%); }
}
.topnav__tray__bar--indeterminate > span {
  width: 35% !important;
  background: linear-gradient(90deg,
    transparent 0%,
    var(--accent) 50%,
    transparent 100%);
  animation: topnav-tray-bar-shimmer 1.6s ease-in-out infinite;
  transition: none;
}
.topnav__tray__empty,
.topnav__notif__empty {
  margin: 0.4rem 0.6rem;
  color: #8c94a0;
  font-size: 0.85rem;
}

/* ---------- Top-nav notifications panel -------------------------------- */
.topnav__notif__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.4rem 0.55rem 0.5rem;
  border-bottom: 1px solid #2b2b33;
  font-size: 0.85rem;
  color: #c5c8d1;
}
.topnav__notif__head-actions {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}
.topnav__notif__mark {
  background: transparent;
  border: 0;
  color: #a0b4ff;
  cursor: pointer;
  font: inherit;
  font-size: 0.78rem;
  padding: 0.2rem 0.4rem;
  border-radius: 4px;
}
.topnav__notif__mark:hover { background: #1c1c24; }
/* "Clear" is destructive — uses a softer red so the user can tell
 * it apart from "Mark all read" at a glance. Hover stays muted so
 * we don't make it scream. */
.topnav__notif__clear { color: #ff8b8b; }
.topnav__notif__clear:hover { background: #2a1a1c; }
.topnav__notif__list {
  max-height: 60vh;
  overflow-y: auto;
}
.topnav__notif__row {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "title time" "body  body";
  gap: 0.2rem 0.5rem;
  padding: 0.5rem 0.6rem;
  border-radius: 6px;
  text-decoration: none;
  color: #d6d8df;
  font-size: 0.85rem;
}
.topnav__notif__row:hover { background: #1c1c24; }
.topnav__notif__row.is-unread {
  background: #1a1f2e;
  border-left: 3px solid #5b8cff;
  padding-left: calc(0.6rem - 3px);
}
.topnav__notif__title {
  grid-area: title;
  font-weight: 600;
  color: #e8e8ee;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topnav__notif__body {
  grid-area: body;
  color: #8c94a0;
  font-size: 0.8rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topnav__notif__time {
  grid-area: time;
  color: #6f7686;
  font-size: 0.75rem;
}

/* ---------- Project card / row summary metrics ------------------------- */
/* Compact inline metrics block under the project card title in grid view
 * + as columns in the list view. Built so a project with no clips/folders
 * just shows "Empty" rather than three zeros. */
.project-card__summary {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  font-size: 0.8rem;
  color: var(--muted);
  margin: 0.4rem 0;
}
.project-card__summary > span {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}
.project-card__summary .is-unread {
  color: #5b8cff;
  font-weight: 600;
}
/* Collaborator pill — small inline chip on the project card showing
 * "N collaborators · M pending". Mirrors the look of the existing
 * unread-comments cell but in a muted accent so the owner reads
 * collaboration state at a glance without it competing with the
 * unread-comments call-to-action. */
.project-card__collab {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  background: rgba(95, 200, 209, 0.12);
  color: #9adde4;
  border: 1px solid rgba(95, 200, 209, 0.3);
  font-size: 0.72rem;
  font-weight: 500;
  white-space: nowrap;
}
.project-card__collab svg { display: block; opacity: 0.9; }
/* List-view variant: sits inline next to the project name, slightly
 * smaller padding so it doesn't push the name row taller. */
.project-card__collab--row {
  margin-left: 0.5rem;
  padding: 0 0.4rem;
  font-size: 0.7rem;
}

/* Manage collaborators modal — list rows + small Remove/Cancel
 * buttons. Lives at the bottom of the file so any future restyle of
 * .modal can override individual properties without specificity wars. */
.manage-list {
  list-style: none;
  padding: 0;
  margin: 0;
  border: 1px solid #232a36;
  border-radius: 8px;
  background: #0f131a;
  max-height: 50vh;
  overflow-y: auto;
}
.manage-list__row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid #1a2030;
}
.manage-list__row:last-child { border-bottom: 0; }
.manage-list__name {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
}
.manage-list__sub {
  color: var(--muted);
  font-size: 0.78rem;
}
.manage-list__role {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 0.78rem;
  text-transform: capitalize;
}
.manage-list__empty,
.manage-list__loading {
  padding: 0.6rem 0.75rem;
  color: var(--muted);
  font-size: 0.85rem;
  font-style: italic;
}
.btn--small {
  padding: 0.25rem 0.55rem;
  font-size: 0.78rem;
  line-height: 1.2;
}

/* Manage collaboration: inline invite row + per-member role dropdown. */
.mm-invite {
  display: flex;
  gap: 0.5rem;
  margin: 0.25rem 0 0.5rem;
}
.mm-invite input[type="email"] {
  flex: 1 1 auto;
  min-width: 0;
}
.mm-invite select { flex: 0 0 auto; }
.mm-role-select {
  flex: 0 0 auto;
  font-size: 0.78rem;
  padding: 0.2rem 0.4rem;
  background: #0f131a;
  color: var(--fg, #e8e8ee);
  border: 1px solid #2a3342;
  border-radius: 6px;
}
.mm-legend {
  margin: 0.85rem 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}
.mm-legend summary {
  cursor: pointer;
  user-select: none;
  color: var(--fg, #cfd3db);
}
.mm-legend ul {
  margin: 0.4rem 0 0;
  padding-left: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.mm-legend strong { color: var(--fg, #e8e8ee); font-weight: 500; }

/* ---------- Profile avatar preview + actions --------------------------- */
.avatar-row {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.avatar-preview {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: #2a2218;
  border: 1px solid #3b3441;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.6rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--accent);
  flex: 0 0 auto;
}
.avatar-preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.avatar-preview--initials {
  background: linear-gradient(135deg, #2a2218, #1a1a21 70%);
}
.avatar-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* ---------- Brand-kit editor (profile + org settings) ----------------- */
/* Logo preview tile is bigger + rectangular than the avatar one — logos
 * are usually wider than tall and need room for the full mark to read. */
.brand-row {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.brand-preview {
  width: 160px;
  height: 80px;
  border-radius: 6px;
  background: #0f131a;
  border: 1px solid #2a3140;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted, #8c94a0);
  flex: 0 0 auto;
  padding: 0.4rem;
}
.brand-preview img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
}
.brand-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* Live preview of the share header with the user's brand applied. */
.brand-live-preview {
  background: #0f131a;
  border: 1px solid #2a3140;
  border-radius: 8px;
  overflow: hidden;
}
.brand-live-preview__header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  background: #12161e;
  border-bottom: 2px solid #3d7d72;
}
.brand-live-preview__lockup {
  display: inline-flex;
  align-items: center;
  font-weight: 500;
  font-size: 1.1rem;
  letter-spacing: -0.04em;
}
.brand-live-preview__title {
  margin-left: auto;
  font-size: 0.9rem;
  color: #8c94a0;
}

/* ======================================================================
 * MOBILE / TOUCH FOUNDATION
 *
 * Everything below this comment is iPhone-friendly polish layered on
 * top of the desktop styles above. Three groups, in this order:
 *   1. (hover: none) — touch devices regardless of width.
 *      We always-reveal chrome that desktop hides until hover, and
 *      bump tap targets to ~44px so fingers can hit them.
 *   2. (max-width: 640px) — phone-class viewports.
 *      Stack two-column layouts, tighten padding, scale the top
 *      nav, respect safe-area-insets so the floating selection
 *      bar doesn't hide under the iOS home indicator.
 *   3. (max-width: 420px) — extra-narrow phones.
 *      Edge-to-edge cards, slimmer toolbar, typography tweaks.
 *
 * Page-specific layouts (library, player, share) get their own
 * mobile passes inside their own stylesheets — this file owns the
 * cross-page pieces (top nav, modals, buttons, inputs).
 * ====================================================================== */

/* ---- Touch devices (any width) ----------------------------------- */
@media (hover: none) {
  /* Desktop hides the ⋯ menu button until the card is hovered.
   * On touch there's no hover, so the button would be invisible
   * forever. Always reveal it. */
  .card-menu-btn { opacity: 1 !important; }

  /* Same for the duration badge / menu-button stacking on the
   * library card thumb — desktop hides duration when the menu
   * appears on hover, but on touch both should stay visible. */
  .library[data-view="grid"] .clip-card .duration-badge {
    opacity: 1 !important;
  }

  /* Bump small interactive targets so a finger can hit them.
   * 44×44 is the iOS HIG minimum; 36px is the floor we'll accept
   * for icon-only chips that stack tightly. */
  .card-menu-btn,
  .player-btn,
  .share-tool-btn {
    min-width: 36px;
    min-height: 36px;
  }
}

/* ---- Phone viewports (≤ 640px) ----------------------------------- */
@media (max-width: 640px) {
  /* Inputs need ≥16px font-size, otherwise iOS Safari zooms in
   * when they're focused. The site's body inputs were 14-15px;
   * lift them on phone to head off the unwanted zoom. */
  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="search"],
  input[type="number"],
  input[type="tel"],
  input[type="url"],
  textarea,
  select {
    font-size: 16px;
  }

  /* Top nav: tighten padding, single-line layout. We DON'T let
   * .topnav__inner wrap on phones — wrapping was pushing the
   * uploads tray + bell + avatar onto a second row right under
   * the brand, and the absolutely-positioned dropdown panels
   * (notif, uploads, search results) ended up overlapping page
   * content because their "right: 0" anchor was below the
   * viewport's right column edge. Single line + hide the
   * omnibox is the cleanest path. */
  .topnav__inner {
    padding: 0.5rem 0.75rem;
    gap: 0.55rem;
    flex-wrap: nowrap;
    align-items: center;
  }
  .topnav__brand { font-size: 1.05rem; flex: 0 0 auto; }
  /* Global omnibox hidden on phone. The brand link itself is the
   * "Projects" route and Cmd-K search is desktop-only chrome anyway.
   * The user can still reach a project from the bell notifications
   * or via direct URLs they have. */
  .topnav__search { display: none; }
  .topnav__right { margin-left: auto; flex: 0 0 auto; }

  /* iOS safe-area handling — give the top nav room for the
   * status bar, and the bottom of the page room for the home
   * indicator. The `env(safe-area-inset-*)` values are zero
   * outside iOS so this is a no-op on Android. */
  .topnav {
    padding-top: env(safe-area-inset-top, 0);
  }

  /* Topnav dropdowns (uploads tray, notifications, account
   * menu): on desktop they're anchored to the right edge of the
   * triggering button with min-width: 320px. On a 375px iPhone
   * that overflows the left edge if the button is anywhere
   * other than the right corner. Pin them to the viewport edges
   * with a fixed-position layout so they always fit. */
  .topnav__tray,
  .topnav__notif,
  .topnav__menu,
  .topnav__search-results {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0) + 52px);
    left: 8px;
    right: 8px;
    width: auto;
    max-width: none;
    min-width: 0;
    max-height: calc(100vh - env(safe-area-inset-top, 0) - 72px);
    overflow-y: auto;
  }

  /* Modals: full-bleed on phones — the centred card with
   * `min-width: 460px` was clipping at 380px viewport. */
  .modal {
    width: calc(100vw - 24px);
    max-width: 480px;
  }
}

/* ---- Extra-narrow phones (≤ 420px) -------------------------------- */
@media (max-width: 420px) {
  /* Tighten the top nav further so the bell + avatar still fit
   * once the search omnibox is showing. */
  .topnav__inner { gap: 0.45rem; padding: 0.4rem 0.6rem; }
  .topnav__brand { font-size: 1rem; }

  /* Body padding on the project / library / profile root
   * containers — most templates use a 1.5rem horizontal padding;
   * pull it tighter so cards run closer to the edges. */
  .container,
  .page,
  .library-page {
    padding-left: 0.75rem;
    padding-right: 0.75rem;
  }
}

/* ---- Page-level overflow guards (≤ 640px) -------------------------
 * Two changes that fix the "page is wider than the viewport, need
 * to pinch-zoom out" symptom on iPhone 15 Pro and friends.
 *
 * 1. The .app container's desktop padding (32px each side) was
 *    eating 64px of a 393px iPhone viewport before any content
 *    rendered. Pull it in to 12px so cards reach the edges and
 *    leave room for the actual content.
 *
 * 2. body { overflow-x: hidden } is a defensive backstop. Any
 *    single rogue child that exceeds the viewport (a long
 *    filename in a `white-space: nowrap` pill, a thumbnail with
 *    a fixed minimum, a flex row that didn't wrap) used to push
 *    the page wide because the body would honour its widest
 *    descendant. Clipping at the body level means the offending
 *    element gets cut off rather than scaling the whole page,
 *    and the user sees the layout the way we designed it.
 *    `.library[data-view="list"]` keeps its own overflow-x: auto
 *    so the user can still horizontal-scroll the list rows
 *    within their own container.
 */
@media (max-width: 640px) {
  .app {
    padding: 0.9rem 0.75rem 2rem;
  }
  /* Pin every page-level container to exactly the viewport width.
   * `overflow-x: clip` is stronger than `hidden` — it doesn't
   * establish a new scrolling context, so iOS Safari can't
   * sidestep it by scrolling sticky positioned elements. The
   * `100vw` lock makes html/body explicitly size to the visual
   * viewport even if a descendant tries to grow them. */
  html, body {
    overflow-x: clip;
    width: 100vw;
    max-width: 100vw;
    margin: 0;
  }
  .topnav,
  .topnav__inner,
  .app,
  .library-page,
  main {
    max-width: 100vw;
    box-sizing: border-box;
    overflow-x: clip;
  }
  /* Belt-and-braces: cap every grid item, card, and form control
   * to the viewport. If something tries to render wider than the
   * page, this clips it instead of letting it push the document. */
  .clip-card,
  .folder-card,
  .share-card,
  .library,
  .library-page__main {
    max-width: 100%;
    min-width: 0;
  }

  /* ---- Projects-page toolbar -----------------------------------------
   * Desktop layout is [count][spacer][Sort: <select>][Icons|List].
   * On a 380px viewport that comfortably overflows — the count
   * duplicates the page subtitle, the Sort label is redundant
   * with the dropdown's own arrow, and the view-toggle's text
   * labels push the cluster off the right edge. Strip the chrome
   * down to just [<select>][icon | icon] and let the row shrink to
   * fit. The same .library-toolbar markup is reused inside the
   * per-project library page, so this declutter applies there too.
   */
  .library-toolbar {
    padding: 0.35rem 0.45rem;
    gap: 0.4rem;
    flex-wrap: nowrap;
    min-width: 0;
  }
  .library-toolbar__count,
  .sort-control__label {
    display: none;
  }
  .library-toolbar__spacer {
    display: none;
  }
  .sort-control {
    flex: 1 1 auto;
    min-width: 0;
  }
  .sort-control__select {
    width: 100%;
    min-width: 0;
    /* iOS Safari forces selects to the option's intrinsic width
     * unless we override with text-overflow + a controlled
     * width. Keep the dropdown short and ellipsis the option
     * text in the closed state. */
    text-overflow: ellipsis;
  }
  /* View toggle: drop the "Icons" / "List" text — the SVG glyphs
   * already read clearly enough as a grid vs rows pair. */
  .library-view-btn span {
    display: none;
  }
  .library-view-btn {
    padding: 0.4rem 0.55rem;
  }
}

/* The floating selection bar is hidden on phones — see
 * library.css's @media (max-width: 640px) block. The desktop
 * positioning above (.selection-bar {position: fixed; bottom: …})
 * is the only state the bar is in on screens larger than 640px. */
