/* Bowie Media Client Portal — design tokens (v2 minimal)
   Record Red — accent ONLY: the tally dot, hover outlines, favorites, NEW.
   Display: Archivo (heavy, tight) · Body: Inter
   Media thumbnails keep a dark "film frame" matte in BOTH themes.

   THEMING
   Every colour below is a token, and the [data-theme="dark"] block near the
   bottom of this section is the only place dark values live. Two tokens carry
   meaning that flips rather than shifts, so don't collapse them back together:
     --bg-elev  a raised surface (card, chip, panel) — light on light, dark on dark
     --on-ink   text sitting ON an --ink fill — inverts with it
   Anything that must stay white regardless of theme (the lightbox chrome, which
   always sits on a black scrim) is a literal #fff on purpose. */

:root {
  color-scheme: light;
  --ink: #1A1A1A;
  --ink-deep: #141414;      /* film matte — deliberately dark in both themes */
  --red: #E8503A;          /* warm vermilion — friendly, leans orange so it never reads pink */
  --red-rgb: 232,80,58;
  --feature-rgb: 45,124,224;   /* the featured file's halo — blue, so it never
                                  reads as an error next to the red accent */
  --feature-line: #BFD4F2;
  --bg: #FFFFFF;
  --bg-elev: #FFFFFF;
  --mist: #F6F6F5;
  --fog: #ECECEA;
  --slate: #6E6E6E;
  --line: #E4E4E2;
  --line-strong: #D4D4D1;   /* hover / emphasis borders */
  --on-ink: #FFFFFF;
  --ok: #1D7A46;
  --warn: #9A6B00;
  --danger: #BF3F2E;
  --fav: #EF6A55;
  --hero-line: #F3C7BF;
  --pin-line: #C9C9C6;
  --btn-line: #DDDDDA;
  --icon-flip: none;        /* dark mode inverts the flat SVG icon set */
  --topbar-bg: #FFFFFF;                /* opaque: a blurred sticky bar costs a repaint per frame */
  --scrim: rgba(255,255,255,.72);      /* hover veil on the recently-added strip */
  --scrim-0: rgba(255,255,255,0);      /* same hue at zero alpha, so the fade is pure alpha */

  /* status pills */
  --st-prog-bg: #FBF4E2; --st-prog-line: #E7D4A8;
  --st-done-bg: #E7F3EC; --st-done-line: #BFE0CC;
  --st-req-bg:  #FFEDED; --st-req-line:  #FFCDCD;

  /* a file whose link no longer works */
  --broken: #C25B5B; --broken-line: #E9CFCF; --broken-bg: #FBF3F3; --broken-ink: #8E4747;
  /* review links (frame.io & co) — amber reads as "awaiting your notes" */
  --review: #B07D2B; --review-line: #EADCBE; --review-bg: #FCF7EC; --review-ink: #8A6220;

  /* watch pages */
  --watch-line: #D9D3F5; --watch-line-a: #DCD6F6; --watch-line-b: #CBE7F7;
  --watch-ink: #4B32C3; --watch-ink-hover: #37209B; --watch-edge: #C9BFF6;
  /* Tinted button variants. A wash of the card's own colour with that colour as
     the ink — the same recipe as .type-chip, so the button that makes a watch or
     review page reads as part of the site rather than a saturated alert. */
  --watch-btn: #F1EDFF; --watch-btn-hover: #E4DCFF; --watch-btn-ink: #4B32C3; --watch-btn-line: #C9BFF6;
  --review-btn: #FCF7EC; --review-btn-hover: #F6EDDA; --review-btn-ink: #8A6220; --review-btn-line: #EADCBE;
  --watch-chip-a: #F1EDFF; --watch-chip-b: #E7F6FF;

  --radius: 12px;
  --shadow: 0 1px 2px rgba(20,20,20,.04);
  --shadow-pop: 0 10px 40px rgba(20,20,20,.10);
  --shadow-lift: 0 10px 24px rgba(0,0,0,.09);
  --display: 'Archivo', system-ui, sans-serif;
  --body: 'Inter', system-ui, sans-serif;
}

/* Dark theme. Set by the inline script in _top.php before first paint, so there
   is no flash; it falls back to the OS preference when nothing is stored. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --ink: #EDEDEB;
  --ink-deep: #0A0A0A;
  --red: #F4705C;
  --red-rgb: 244,112,92;
  --feature-rgb: 106,166,255;
  --feature-line: #35507A;
  --bg: #101011;
  --bg-elev: #191919;
  --mist: #1D1D1E;
  --fog: #272728;
  --slate: #9C9C99;
  --line: #2E2E30;
  --line-strong: #414143;
  --on-ink: #141414;
  --ok: #56C98A;
  --warn: #D9A94A;
  --danger: #F0796A;
  --fav: #FF8C7A;
  --hero-line: #5E2727;
  --pin-line: #4A4A4C;
  --btn-line: #37373A;
  --icon-flip: invert(1) hue-rotate(180deg);
  --topbar-bg: #101011;
  --scrim: rgba(16,16,17,.78);
  --scrim-0: rgba(16,16,17,0);

  --st-prog-bg: #3A2F16; --st-prog-line: #5B4A22;
  --st-done-bg: #12331F; --st-done-line: #1F5334;
  --st-req-bg:  #3B1A1A; --st-req-line:  #5E2727;

  --broken: #D97070; --broken-line: #5A2E2E; --broken-bg: #2A1818; --broken-ink: #E39A9A;
  --review: #C99B45; --review-line: #4A3B1E; --review-bg: #241D11; --review-ink: #E3C177;

  --watch-line: #3A3363; --watch-line-a: #403867; --watch-line-b: #2B4A5C;
  --watch-ink: #C3B6FF; --watch-ink-hover: #DCD4FF; --watch-edge: #4B4080;
  --watch-btn: #221D3B; --watch-btn-hover: #2C2550; --watch-btn-ink: #C3B6FF; --watch-btn-line: #4B4080;
  --review-btn: #241D11; --review-btn-hover: #33291A; --review-btn-ink: #E3C177; --review-btn-line: #4A3B1E;
  --watch-chip-a: #221D3B; --watch-chip-b: #16303C;

  --shadow: 0 1px 2px rgba(0,0,0,.5);
  --shadow-pop: 0 10px 40px rgba(0,0,0,.6);
  --shadow-lift: 0 10px 24px rgba(0,0,0,.55);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--body); font-size: 15px; line-height: 1.55;
}
img { max-width: 100%; display: block; }
a { color: var(--ink); }
h1, h2, h3 { font-family: var(--display); line-height: 1.12; margin: 0 0 .5em; }
h1 { font-weight: 800; letter-spacing: -.01em; }
button { font: inherit; cursor: pointer; }
input, select { font: inherit; }
[hidden] { display: none !important; }
.inline { display: inline; }
.muted { color: var(--slate); }
.small { font-size: 12.5px; }
.nowrap { white-space: nowrap; }
.ta-r { text-align: right; }

:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---------- the tally light ---------- */
.rec-dot { color: var(--red); font-size: .72em; vertical-align: .12em; }
.pulse { animation: rec 2.2s ease-in-out infinite; display: inline-block; }
@keyframes rec { 0%,100% { opacity: 1 } 50% { opacity: .25 } }

/* ---------- chrome ---------- */
.topbar {
  /* Opaque, not a blurred translucent pane. A full-width backdrop-filter on a
     sticky bar re-blurs the strip behind it every frame: it took the worst
     scroll frame from 17ms to 124ms. The bar reads the same solid. */
  background: var(--topbar-bg); color: var(--ink);
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px clamp(16px, 4vw, 40px); gap: 12px; position: sticky; top: 0; z-index: 40;
}
.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; color: var(--ink); flex-shrink: 0; }
/* the mark is transparent-backed, so it sits bare on light surfaces — no tile frame */
.brand-mark { width: 34px; height: 34px; flex-shrink: 0; }
/* A member's own logo is a wordmark as often as a square, so height is the only
   fixed dimension — width follows, capped so a wide one can't crowd out the nav.
   It fills the bar rather than floating in the middle of it: at 30px a wordmark
   read as an afterthought next to the account menu. The bar keeps its height —
   the padding gives back exactly what the taller mark takes, so 52 + 4 + 4 is
   the same 61px bar as 30 + 12 + 12 was, with the mark now nearly all of it.

   This assumes the stored logo is tight to its artwork; store_org_logo() trims
   transparent margins on upload for exactly that reason. A padded canvas will
   still look small here no matter what this number says, because the padding
   scales with it — fix such a logo by re-uploading it, not by raising this. */
/* The member's mark sits on its own white plate, so it reads as a button rather
   than a bare image. It is white in BOTH themes on purpose: the icon set gets
   inverted in dark mode, but a member's own artwork must never be — a dark
   wordmark would simply disappear into the dark bar. The plate gives every logo
   the same footprint whatever shape it is, and the hairline ring keeps it
   visible against the white light-mode bar, where plate and bar are both #FFF. */
.brand-mark-custom {
  width: auto; height: 52px; max-width: 240px;
  object-fit: contain; object-position: center;
  box-sizing: border-box; padding: 5px 10px;
  background: #FFFFFF; border-radius: 10px;
  box-shadow: inset 0 0 0 1px rgba(17, 17, 17, .10);
  transition: box-shadow .15s ease;
}
.brand:hover .brand-mark-custom { box-shadow: inset 0 0 0 1px rgba(17, 17, 17, .22); }
/* Desktop only — below 641px the bar owns its own padding, because padding-top
   there is max(10px, safe-area-inset-top) and must stay that way for the notch. */
@media (min-width: 641px) { .topbar:has(.brand-mark-custom) { padding-top: 4px; padding-bottom: 4px; } }
@media (max-width: 640px) { .brand-mark-custom { max-width: 150px; height: 38px; padding: 4px 8px; border-radius: 8px; } }
/* nav sits right, next to the account menu, exactly as before the split */
.topnav { display: flex; align-items: center; gap: 18px; min-width: 0; margin-left: auto; }
.topbar > .who-menu { margin-left: 18px; }
.topnav a { color: var(--slate); text-decoration: none; font-size: 13.5px; font-weight: 500; white-space: nowrap; }
.topnav a:hover { color: var(--ink); }
.topnav .btn { white-space: nowrap; }
/* Keyboard focus — one deliberate ring everywhere (inputs keep their border treatment) */
:is(a, button, summary, .chip, .vt-btn):focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 6px; }

.who-menu { position: relative; }
.who-menu::after { content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 10px; }
.who-btn { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who-caret {
  display: inline-block; margin-left: 4px; width: 0; height: 0;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid var(--slate); vertical-align: middle;
}
.who-drop {
  position: absolute; right: 0; top: calc(100% + 8px); min-width: 170px; z-index: 60;
  background: var(--bg); border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 12px 32px rgba(0,0,0,.10); padding: 6px; display: none;
}
.who-menu:hover .who-drop, .who-menu:focus-within .who-drop, .who-menu.is-open .who-drop { display: block; }
.who-item {
  display: block; width: 100%; text-align: left; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 500; color: var(--ink); text-decoration: none;
  padding: 9px 12px; border-radius: 9px;
}
.who-item:hover { background: var(--mist); }
.who-signout:hover { color: var(--red); }
@media (max-width: 640px) {
  .topnav { gap: 12px; }
}

.page { min-height: 62vh; }
.footer {
  margin-top: 64px; border-top: 1px solid var(--line);
  padding: 22px clamp(16px, 4vw, 40px); display: flex; justify-content: space-between; gap: 12px;
  color: var(--slate); font-size: 13px; flex-wrap: wrap; align-items: center;
}
.footer a { color: var(--slate); }
/* The contact link is a button, so it has to opt back out of the slate link
   colour above — that rule is what makes every other footer link recede. */
.footer .footer-contact { color: var(--ink); text-decoration: none; }

.flashes { padding: 12px clamp(16px, 4vw, 40px) 0; display: grid; gap: 8px; max-width: 1240px; margin: 0 auto; }
.flash { border-radius: var(--radius); padding: 11px 14px; font-size: 14px; border: 1px solid var(--line); background: var(--bg); overflow-wrap: anywhere; }
.flash-ok { border-left: 3px solid var(--ok); }
.flash-err { border-left: 3px solid var(--red); }
.flash-warn { border-left: 3px solid var(--warn); }

/* ---------- buttons — quiet gray, red outline on hover ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  border: 1px solid var(--line); border-radius: 10px; padding: 10px 18px;
  background: var(--mist); color: var(--ink);
  font-weight: 600; font-size: 14px; text-decoration: none;
  transition: border-color .15s ease, background .15s ease, transform .06s ease;
}
/* Red on EVERY button hover meant dragging the pointer across a five-button
   header strobed the accent across the whole row. A darker line is the same
   signal without the alarm; --red now belongs to destructive and to brand. */
.btn:hover { border-color: var(--line-strong); }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--fog); border-color: var(--btn-line); font-weight: 700; }
.btn-primary:hover { border-color: var(--line-strong); }
.btn-ghost { background: transparent; }
.btn-ghost:hover { border-color: var(--line-strong); background: var(--mist); }
.btn-ghost-light { background: transparent; }
.btn-ghost-light:hover { border-color: var(--line-strong); background: var(--mist); }
.btn-danger { background: transparent; color: var(--danger); border-color: var(--line); }
.btn-danger:hover { border-color: var(--red); }
.btn-sm { padding: 7px 12px; font-size: 13px; }
.btn-xs { padding: 3px 8px; font-size: 12px; }
.btn-block { width: 100%; }

/* ---------- auth ---------- */
.auth-body { background: var(--bg); }
.auth-body .footer { display: none; }
.auth-split { display: grid; grid-template-columns: 1fr 1fr; min-height: 100vh; min-height: 100dvh; }
.auth-hero {
  background: var(--mist); color: var(--ink); border-right: 1px solid var(--line);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 26px; padding: 40px;
}
.auth-logo { width: min(190px, 44%); }
.auth-tag { font-family: var(--display); font-weight: 700; letter-spacing: .22em; font-size: 12px; color: var(--slate); text-align: center; }
.auth-form { display: flex; align-items: center; justify-content: center; padding: 40px 20px; }
.authwrap { display: flex; justify-content: center; padding: 9vh 20px 40px; }
.authcard { width: 100%; max-width: 420px; background: var(--bg); border: 1px solid var(--line); border-radius: 16px; padding: 30px; }
.authcard h1 { font-size: 24px; }
.authcard label { display: block; font-size: 13px; font-weight: 600; margin: 14px 0 5px; }
.authcard input { width: 100%; border: 1px solid var(--line); border-radius: 10px; padding: 11px 12px; background: var(--mist); }
.authcard input:focus { border-color: var(--ink); outline: none; background: var(--bg); }
.authcard .btn { margin-top: 20px; }
.authlinks { margin-top: 16px; font-size: 13.5px; }
@media (max-width: 800px) {
  .auth-split { grid-template-columns: 1fr; }
  .auth-hero { padding: 34px 20px; min-height: 180px; border-right: 0; border-bottom: 1px solid var(--line); }
  .auth-logo { width: 100px; }
}

/* ---------- marquee (client hero) ----------
   Flat on purpose: no tinted band, no drop shadow. The hairline under the
   section and the one around the still do the separating. */
.marquee { background: var(--bg); color: var(--ink); border-bottom: 1px solid var(--line); }
.marquee-inner {
  display: grid; grid-template-columns: minmax(280px, 1fr) minmax(300px, 520px);
  gap: clamp(22px, 4vw, 48px); align-items: center;
  padding: clamp(26px, 4vw, 52px) clamp(16px, 4vw, 40px);
  max-width: 1240px; margin: 0 auto;
}
.marquee-min .marquee-inner { grid-template-columns: 1fr; padding-block: 30px; }
/* the empty state has no CTA or stats — don't leave their grid rows behind */
.marquee-min .marquee-copy { display: block; }
.eyebrow { font-family: var(--display); font-weight: 700; letter-spacing: .24em; font-size: 11.5px; color: var(--slate); margin: 0 0 12px; }
/* The CTA rides beside the title so the block loses a whole stacked row.
   Laid out as areas so it can drop below the stats on narrow screens
   instead of wedging itself between the title and them. */
.marquee-copy {
  display: grid; justify-content: start; align-items: center;
  grid-template-columns: minmax(0, auto) auto;
  grid-template-areas: "eyebrow eyebrow" "title cta" "meta meta";
  gap: 12px 18px;
}
.marquee-copy .eyebrow { grid-area: eyebrow; margin: 0; }
.marquee-title {
  grid-area: title;
  font-size: clamp(26px, 3.6vw, 42px); font-weight: 800; line-height: 1.06;
  color: var(--ink); margin: 0; letter-spacing: -.02em; text-wrap: balance;
}
.marquee-cta { grid-area: cta; }
/* status and stats sit on one quiet line rather than stacking */
.marquee-meta {
  grid-area: meta;
  display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap;
  margin: 0; color: var(--slate); font-size: 13.5px;
}
.marquee-fresh { color: var(--ink); font-weight: 600; }
/* rounder, with a soft halo instead of a hard drop shadow */
.marquee-art .card-matte {
  border-radius: 18px; border: 1px solid var(--line);
  box-shadow: 0 12px 34px -14px rgba(20,20,20,.22), 0 2px 10px rgba(20,20,20,.05);
}
@media (max-width: 860px) { .marquee-inner { grid-template-columns: 1fr; } .marquee-art { order: -1; } }
/* too narrow for a title-and-button row — send the CTA to the bottom */
@media (max-width: 620px) {
  .marquee-copy {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "eyebrow" "title" "meta" "cta";
  }
  .marquee-cta { justify-self: start; margin-top: 6px; }
}

/* ---------- film-frame matte (stays dark on purpose) ---------- */
.card-matte {
  position: relative; aspect-ratio: 16 / 9; background: var(--ink-deep);
  border-radius: var(--radius); overflow: hidden;
}
.card-art { width: 100%; height: 100%; object-fit: cover; }
.card-art-icon { object-fit: cover; opacity: 1; }
.card-matte:has(.card-art-icon) { background: var(--mist); }
.matte-open { display: block; width: 100%; height: 100%; }
.card:has(.matte-open), .card:has(button[data-embed]) { cursor: pointer; }
.card-title a { color: inherit; text-decoration: none; }
.card-title a:hover { text-decoration: underline; }
/* Type badge over real thumbnails — instant "what kind of file is this" */
.type-badge {
  position: absolute; left: 8px; bottom: 8px; width: 30px; height: 30px; z-index: 2;
  border-radius: 9px; border: 1px solid var(--line); background: var(--bg-elev);
  box-shadow: 0 2px 10px rgba(0,0,0,.20);
}
.strip-row .type-badge { width: 24px; height: 24px; border-radius: 7px; left: 6px; bottom: 6px; }
.grid.view-list .type-badge { width: 20px; height: 20px; border-radius: 6px; left: 5px; bottom: 5px; }
.new-badge {
  position: absolute; top: 10px; left: 10px; background: var(--bg-elev); color: var(--ink);
  font-family: var(--display); font-weight: 800; letter-spacing: .14em; font-size: 10.5px;
  padding: 5px 9px; border-radius: 999px;
}
/* ---------- corner marks (pin + star) ---------- */
.card-marks { position: absolute; top: 8px; right: 8px; display: flex; gap: 6px; z-index: 2; }
/* ---------- pins & stars ----------
   One treatment site-wide: a white chip with a dark glyph, filling in on. The
   glyphs are SVG in currentColor, so nothing here needs a filter to recolour
   them — which is what made every context look slightly different before. */
.mark {
  width: 30px; height: 30px; border-radius: 999px; padding: 0;
  display: grid; place-items: center; line-height: 1;
  background: rgba(255,255,255,.92); color: var(--slate);
  border: 1px solid rgba(0,0,0,.10);
  box-shadow: 0 1px 3px rgba(0,0,0,.14);
  cursor: pointer; opacity: .85;
  transition: opacity .15s, color .15s, background .15s, transform .1s;
}
.mark svg { display: block; width: 12px; height: 12px; }
.card:hover .mark, .mark:focus-visible, .client-item:hover .mark { opacity: 1; }
.mark:hover { transform: scale(1.08); background: #fff; color: var(--ink); }
.mark.is-on, .mark.is-fav { opacity: 1; background: #fff; color: var(--ink); }
.mark.is-fav { color: var(--fav); }
.card.is-pinned { border-color: var(--pin-line); }
.card-sm .card-marks { top: 6px; right: 6px; gap: 4px; }
.card-sm .mark { width: 25px; height: 25px; font-size: 12px; }

.tag-row { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; margin: 0; }
.card-sm .tag-row .type-chip:nth-child(n+3) { display: none; }

/* ---------- admin tag suggestions ---------- */
.tag-suggest { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }
.tag-suggest:empty { display: none; }
.chip-add { font-size: 11.5px; padding: 4px 10px; }
.chip-add.is-on, .chip-append.is-on { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }
.tag-custom-wrap { display: block; margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); }
.tag-newrow { display: flex; gap: 6px; align-items: center; margin-top: 7px; }
.tag-newrow input { flex: 0 1 240px; border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px; font-size: 12.5px; background: var(--mist); }
.tag-newrow input:focus { border-color: var(--ink); outline: none; background: var(--bg); }

/* ---------- admin danger zone (org settings) ---------- */
.danger-zone { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line); display: grid; gap: 12px; }
.danger-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.danger-title { margin: 0 0 2px; font-weight: 600; font-size: 14px; }
.delete-confirm { border: 1px solid var(--broken-line); background: var(--broken-bg); border-radius: 12px; padding: 14px; }
.delete-confirm .danger-title { color: var(--danger); }
#archived-toggle.is-on { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }
.list-tools { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 6px 0 2px; }
.list-tools-right { justify-content: flex-end; }
.list-tools #client-search, .list-tools #proj-search { margin: 0; }

/* ---------- admin clients list ---------- */
.list-tools #client-search, .list-tools #proj-search {
  flex: 1 1 240px; max-width: 360px; border: 1px solid var(--line); border-radius: 999px;
  padding: 9px 16px; background: var(--mist); font-size: 14px;
}
.list-tools #client-search:focus, .list-tools #proj-search:focus { background: var(--bg); border-color: var(--ink); outline: none; }
.client-item { position: relative; border-bottom: 1px solid var(--line); }
.client-item:first-child { border-top: 1px solid var(--line); }
.client-item.is-off { opacity: .6; }
.client-link {
  display: flex; align-items: center; gap: 12px; padding: 12px 46px 12px 4px;
  text-decoration: none; color: inherit;
}
.client-link:hover { background: var(--mist); }
.client-item:hover .client-pin, .client-pin:focus-visible { opacity: 1; }
.client-manage {
  position: absolute; top: calc(50% - 17px); right: 44px;
  opacity: 0; background: var(--bg-elev);
}
.client-item:hover .client-manage, .client-manage:focus-visible { opacity: 1; }

.client-item.is-pinned { background: var(--mist); }
.empty-row { padding: 14px 4px; }

/* ---------- admin condensed collapsible rows ---------- */
.rows { list-style: none; margin: 6px 0 0; padding: 0; border-top: 1px solid var(--line); }
.row-item { border-bottom: 1px solid var(--line); }
.row-item.is-off { opacity: .6; }
.disc > summary { list-style: none; cursor: pointer; }
.disc > summary::-webkit-details-marker { display: none; }
.disc-sum {
  display: flex; align-items: center; gap: 12px; padding: 11px 4px;
}
.disc-sum:hover { background: var(--mist); }
.avatar.sm { width: 30px; height: 30px; font-size: 13px; }
.disc-main { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 1px; }
.disc-name { font-weight: 600; font-size: 14.5px; }
.disc-sub { font-size: 12.5px; color: var(--slate); overflow-wrap: anywhere; }
.disc-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.chev { color: var(--slate); font-size: 18px; transition: transform .15s ease; flex-shrink: 0; }
.disc[open] > summary .chev { transform: rotate(90deg); }
.disc-body { padding: 4px 4px 16px; }
.disc-body .stack-label { margin-top: 10px; }
.disc-body .invite-actions { margin-top: 12px; }
.inline-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--line); }
.preview-link { text-decoration: none; }
@media (max-width: 560px) { .disc-sub { display: none; } }

.proj-settings { margin-top: 12px; border-top: 1px dashed var(--line); padding-top: 10px; }
.proj-settings > summary { list-style: none; cursor: pointer; font-size: 12.5px; font-weight: 600; color: var(--slate); padding: 4px 0; }
.proj-settings > summary::-webkit-details-marker { display: none; }
.proj-settings > summary::before { content: '⚙ '; }
.proj-settings > summary:hover { color: var(--ink); }
.proj-settings[open] > summary { margin-bottom: 10px; }

/* ---------- admin add-file in member view ---------- */
.addlink.admin-add { border-color: var(--ink); background: var(--bg); }
.proj-link { text-decoration: none; color: inherit; }
.proj-link:hover { color: var(--ink); }
/* actions cluster to the right and wrap as one block, never orphaning a button */
.proj-head-actions {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  justify-content: flex-end; margin-left: auto;
}

/* ---------- project banner + admin card tools ---------- */
/* Slim, wide, clean banner — a strip, not a hero */
.project-banner { max-width: 1240px; margin: 16px auto 0; padding: 0 clamp(16px, 4vw, 40px); }
.project-banner img {
  width: 100%; max-height: 200px; object-fit: cover; border-radius: 14px;
  aspect-ratio: 16 / 4; background: var(--ink-deep);
  border: 1px solid var(--line); box-shadow: var(--shadow);
}
.admin-card-tools { display: inline-flex; gap: 6px; margin-left: auto; }

/* ---------- folders (groups) inside a project ---------- */
.group-head {
  grid-column: 1 / -1; display: flex; align-items: center; gap: 8px;
  margin: 14px 0 0; padding-top: 18px; border-top: 1px solid var(--line);
  font-family: var(--display); font-size: 13px; font-weight: 800;
  letter-spacing: .13em; text-transform: uppercase; color: var(--slate);
}
.group-head svg { flex: 0 0 auto; }
/* Accordion folders — Show/Hide pill on each heading */
.group-head { cursor: pointer; -webkit-user-select: none; user-select: none; }
.group-head:hover { color: var(--ink); }
.group-head::after {
  content: 'Hide ▴'; order: 1; margin-left: 4px;
  font-size: 9px; font-weight: 700; letter-spacing: .1em; color: var(--slate);
  border: 1px solid var(--line); border-radius: 999px; background: var(--bg-elev);
  padding: 4px 10px; line-height: 1;
}
.group-head:hover::after { color: var(--ink); border-color: var(--slate); }
.group-head.is-collapsed::after { content: 'Show ▾'; color: var(--ink); }
.group-head .gh-del { order: 2; }
#file-grid:not(.is-filtering) .card.grp-collapsed { display: none !important; } /* searching reveals collapsed folders */

/* People on this project — tap to email */
/* the people on a project are metadata, not controls — quieter, and below the title */
.proj-team { display: flex; gap: 6px; flex-wrap: wrap; margin: 12px 0 0; }
.team-chip {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--bg-elev);
  padding: 3px 10px 3px 3px; font-size: 12px; font-weight: 500; color: var(--slate);
  text-decoration: none;
}
.team-chip:hover { border-color: var(--line-strong); color: var(--ink); }
.team-chip-avatar {
  width: 20px; height: 20px; border-radius: 999px; background: var(--mist); border: 1px solid var(--line);
  display: grid; place-items: center; font-size: 10px; font-weight: 700; color: var(--slate);
}
.tag-group { background: var(--mist); border: 1px solid var(--line); }
/* Drag & drop (admin preview): heads light up as drop zones */
.card[draggable="true"] { cursor: grab; }
.card.dragging { opacity: .45; cursor: grabbing; }
.is-dragging .group-head, .drop-ungroup {
  border: 1.5px dashed var(--line); border-radius: 12px; padding: 12px 14px; margin-top: 10px;
}
.is-dragging .group-head { border-top-width: 1.5px; }
.drop-ungroup {
  grid-column: 1 / -1; display: flex; align-items: center; gap: 8px;
  font-family: var(--display); font-size: 12px; font-weight: 800;
  letter-spacing: .13em; text-transform: uppercase; color: var(--slate);
}
.group-head.drop-hot, .drop-ungroup.drop-hot {
  border-color: var(--red); color: var(--ink); background: var(--mist);
}
.card.just-moved { animation: bm-moved .9s ease; }
@keyframes bm-moved {
  0% { box-shadow: 0 0 0 3px var(--red); }
  100% { box-shadow: 0 0 0 6px rgba(var(--red-rgb),0); }
}
/* Admin preview cards carry 5 controls — wrap the row instead of cropping it */
/* In any grid, buttons wrap onto extra rows rather than hiding behind a scrollbar.
   The horizontal strip keeps its scroll — it's meant to be a single tight row. */
/* One row, always. Wrapping was what turned the action rail into a block. */
.grid .card-actions { flex-wrap: nowrap; overflow: visible; min-width: 0; }
/* No button chrome inside a card at all — just the word and the icon. A grid of
   filled pills reads as a wall of buttons and eats the space the artwork wants.
   Spacing does the separating; colour and weight do the emphasis. */
.grid:not(.view-list) .card-actions { gap: 14px; }
.grid:not(.view-list) .card-actions .btn {
  background: none; border-color: transparent; box-shadow: none;
  padding: 2px 0; font-size: 12.5px; color: var(--slate);
}
.grid:not(.view-list) .card-actions .btn:hover {
  background: none; border-color: transparent; color: var(--ink); text-decoration: underline;
  text-underline-offset: 3px;
}
/* The row is two groups, not one queue: what you DO with the file on the left,
   what you FILE it under on the right. Left to flow, pin and star landed wherever
   the preceding buttons happened to end — a card with a Download sat 77px further
   along than one without, so the marks never formed a column down the grid. An
   auto margin on the first mark parks the pair against the right edge, which is
   the one position every card shares whatever it carries. */
.grid:not(.view-list) .card-actions .mark.pin { margin-left: auto; }
/* Copy belongs to that right-hand group too — it acts on the file's link, not on
   the file — so the break goes before it and copy/pin/star become three fixed
   columns instead of two. Without this, copy still slid 77px whenever a card
   happened to carry a Download. When there is no copy glyph the pin keeps the
   auto margin above and the pair lands in the same place regardless. */
.grid:not(.view-list) .card-actions .copy { margin-left: auto; }
.grid:not(.view-list) .card-actions:has(.copy) .mark.pin { margin-left: 0; }
/* Same reason, other end: Open / Play / View are different widths, so the copy
   glyph after them drifted card to card. A floor wide enough for the longest of
   the three lines them all up, and flex-start keeps the WORD left-aligned —
   centring it inside the wider box would just move the ragged edge to the text. */
.grid:not(.view-list) .card-actions .btn-primary {
  color: var(--ink); font-weight: 700;
  min-width: 42px; justify-content: flex-start; text-align: left;
}
.grid:not(.view-list) .card-actions .btn-danger { color: var(--danger); }
.grid:not(.view-list) .card-actions .btn-danger:hover { color: var(--danger); }
/* the icon buttons lose their box too */
.grid:not(.view-list) .copy.is-icon { width: auto; height: auto; padding: 2px 0; }
.grid:not(.view-list) .card-more { padding: 2px 0; letter-spacing: 1.5px; }
.grid:not(.view-list) .card-more.is-open { background: none; color: var(--ink); border-color: transparent; }
.grid:not(.view-list) .card-actions .move-select {
  border-color: transparent; background: none; font-size: 12.5px; padding: 2px 0; color: var(--slate);
}
.grid .card-actions .btn { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.grid .card-more { flex: 0 0 auto; margin-left: auto; }
/* Cards show the copy glyph alone so the whole rail fits one line; list rows
   have the width for the words. A real flex box centres it — the old
   font-size:0 trick left the glyph riding high on a collapsed line box. */
.copy.is-icon { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.copy.is-icon svg { display: block; flex: 0 0 auto; }
.grid:not(.view-list) .copy.is-icon { width: 30px; height: 30px; padding: 0; }
.grid:not(.view-list) .copy.is-icon .copy-word,
.grid:not(.view-list) .copy-pass.is-icon .copy-word,
.grid:not(.view-list) .cc-btn.is-icon .copy-word { display: none; }
.grid.view-list .copy.is-icon svg { display: none; }
/* The ··· menu covers its own card rather than floating beside it. No
   positioning maths, nothing to clip it, and it works the same on every card. */
.admin-card-tools { display: inline-flex; }
.card-more { padding: 4px 9px; letter-spacing: 1px; line-height: 1; }
.card-more.is-open { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }
/* ---------- the card options sheet — ONE surface at every width ----------
   This used to be two different things: a bottom sheet on a phone and a cramped
   overlay INSIDE the card on desktop. The overlay was also genuinely broken: it
   sat in the page's stacking order, so on a card near the top of the screen the
   sticky topbar covered its buttons and ate the clicks — which is why Copy link
   "stopped working". Fixed + parked on <body>, nothing covers it. */
.card-sheet {
  position: fixed; inset: 0; z-index: 85; cursor: pointer;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: stretch;
  background: rgba(18,18,18,0); transition: background .2s ease;
}
.card-sheet.is-up { background: rgba(18,18,18,.55); }
.card-sheet[hidden] { display: none; }
.card-sheet-panel {
  cursor: auto; position: relative;
  width: 100%; max-height: 82vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
  display: flex; flex-direction: column; gap: 8px;
  background: var(--bg-elev); color: var(--ink);
  border-top-left-radius: 18px; border-top-right-radius: 18px;
  border-top: 1px solid var(--line-strong);
  padding: 8px 16px calc(20px + env(safe-area-inset-bottom));
  box-shadow: 0 -18px 40px rgba(0,0,0,.28);
}
.card-sheet-head {
  display: flex; align-items: center; gap: 9px;
  /* the close X is absolute in this corner: 34px tall + its 14px offset. The
     head reserves that full footprint so the first action row clears it. */
  padding-right: 52px; min-height: 34px;
}
.sheet-icon { width: 18px; height: 18px; border-radius: 5px; object-fit: contain; flex: 0 0 auto; }
.card-sheet-title { font-weight: 700; font-size: 15px; line-height: 1.3; overflow-wrap: anywhere; }
.card-sheet .menu-close {
  position: absolute; top: 14px; right: 14px; z-index: 2;
  width: 34px; height: 34px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--bg); color: var(--slate); font-size: 20px; line-height: 1; cursor: pointer;
}
.card-sheet .menu-close:hover { color: var(--ink); border-color: var(--ink); }
/* one full-width row per action, at a real target size, everywhere */
.card-sheet-actions { display: flex; flex-direction: column; gap: 8px; width: 100%; }
.card-sheet form { display: block; width: 100%; }
.card-sheet .btn, .card-sheet .move-select {
  width: 100%; justify-content: flex-start; text-align: left; display: flex; align-items: center;
  padding: 12px 14px; font-size: 14.5px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
}
.card-sheet .btn:hover { border-color: var(--ink); }
.card-sheet .btn-primary { background: var(--ink); color: var(--on-ink); border-color: var(--ink); }
.card-sheet .btn-danger { color: var(--danger); }
.card-sheet .btn-danger:hover { border-color: var(--danger); background: var(--broken-bg); }
.card-sheet .move-select { display: block; cursor: pointer; }
.card-sheet .copy.is-icon svg { margin-right: 10px; flex: 0 0 auto; }
/* beats .grid:not(.view-list) .copy.is-icon, which squeezes it to an icon */
.grid:not(.view-list) .card-sheet .copy.is-icon,
.grid:not(.view-list) .card-sheet .copy-pass.is-icon,
.grid:not(.view-list) .card-sheet .cc-btn.is-icon {
  padding: 12px 14px; width: 100%; height: auto; justify-content: flex-start;
}
.grid:not(.view-list) .card-sheet .copy.is-icon .copy-word,
.grid:not(.view-list) .card-sheet .copy-pass.is-icon .copy-word,
.grid:not(.view-list) .card-sheet .cc-btn.is-icon .copy-word { display: inline; }
/* the tile hides these labels to save room; in the sheet there is room */
.grid.size-s:not(.view-list) .card-sheet .lbl-full { display: inline; }
.grid.size-s:not(.view-list) .card-sheet .lbl-short { display: none; }
.grid.size-s:not(.view-list) .card-sheet .open-alt,
.grid.size-s:not(.view-list) .card-sheet .cc-btn,
.grid.size-s:not(.view-list) .card-sheet .dl-btn { display: flex; }
/* the whole tile is a doorway to this sheet now */
.grid:not(.view-list) .card { cursor: pointer; }
/* One gesture per card, at EVERY width now. The title is an <a> to the file,
   which fought the card-opens-a-sheet model: clicking the words did something
   different from clicking two pixels beside them. The click falls through to
   the card and the sheet carries Open. The thumbnail keeps its direct link —
   it reads as the file itself, and behaves the same on desktop and phone. */
.grid:not(.view-list) .card-title a { pointer-events: none; color: inherit; }
/* Move-to-folder menu on admin cards */
.move-select {
  font: inherit; font-size: 13px; font-weight: 600; color: var(--ink);
  border: 1px solid var(--line); border-radius: 10px; background: var(--bg-elev);
  padding: 7px 12px; cursor: pointer;
}
.move-select:hover { border-color: var(--slate); }
.grid.size-s .move-select, .grid.size-m .move-select { padding: 5px 9px; font-size: 11.5px; }
.grid.view-list .move-select { padding: 4px 9px; font-size: 11.5px; }
/* Delete-folder × on group headings (admin preview) */
.gh-del {
  flex: 0 0 auto; width: 22px; height: 22px; margin-left: 6px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 999px; background: var(--bg-elev);
  cursor: pointer; color: var(--slate); font-size: 13px; line-height: 1; font-family: var(--body); font-weight: 400;
}
.gh-del:hover { color: var(--red); border-color: var(--red); }
.gh-del.confirming {
  width: auto; padding: 0 11px; background: var(--red); border-color: var(--red); color: #fff;
  font-family: var(--display); font-weight: 800; font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
}
.archived-badge {
  position: absolute; top: 10px; left: 10px; z-index: 2;
  background: var(--ink); color: var(--on-ink); font-family: var(--display); font-weight: 800;
  letter-spacing: .1em; font-size: 9.5px; padding: 5px 9px; border-radius: 999px;
}
.is-archived-card { opacity: .62; }
.is-archived-card:hover { opacity: 1; }

/* ---------- project status pill ---------- */
.status-badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--display); font-weight: 700; font-size: 10.5px;
  letter-spacing: .06em; text-transform: uppercase;
  padding: 3px 9px 3px 8px; border-radius: 999px; border: 1px solid var(--line);
  white-space: nowrap; vertical-align: middle; line-height: 1.4;
}
.status-badge::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor;
}
.status-in_progress { color: var(--warn); background: var(--st-prog-bg); border-color: var(--st-prog-line); }
.status-completed   { color: var(--ok);   background: var(--st-done-bg); border-color: var(--st-done-line); }
.status-requested   { color: var(--red); background: var(--st-req-bg); border-color: var(--st-req-line); }
.head-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.invite-panel textarea {
  width: 100%; font: inherit; color: var(--ink); resize: vertical;
  border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; background: var(--bg);
}
.invite-panel textarea:focus { border-color: var(--ink); outline: none; }
.folder-head h2 .status-badge { font-size: 11px; transform: translateY(-2px); }

/* ---------- toolbar ---------- */
.toolbar {
  max-width: 1240px; margin: 0 auto; padding: 14px clamp(16px, 4vw, 40px) 2px;
  display: grid; gap: 12px;
}
.toolbar input[type="search"] {
  width: 100%; max-width: 420px; border: 1px solid var(--line); border-radius: 999px;
  padding: 10px 18px; background: var(--mist);
}
.toolbar input[type="search"]:focus { background: var(--bg); border-color: var(--ink); outline: none; }
.toolbar-top { display: flex; align-items: center; gap: 12px; }

/* ---------- dedicated project filter (All files) ---------- */
.proj-filter {
  display: flex; align-items: center; gap: 2px; border-bottom: 1px solid var(--line);
  overflow-x: auto; scrollbar-width: none; margin-bottom: 4px;
}
.proj-filter::-webkit-scrollbar { display: none; }
.proj-filter-label {
  font-family: var(--display); font-weight: 800; font-size: 10.5px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--slate); padding-right: 12px; white-space: nowrap;
}
.proj-tab {
  border: 0; background: none; font: inherit; font-size: 13.5px; font-weight: 600;
  color: var(--slate); padding: 10px 12px; border-bottom: 2px solid transparent;
  margin-bottom: -1px; cursor: pointer; white-space: nowrap;
}
.proj-tab:hover { color: var(--ink); }
.proj-tab.is-on { color: var(--ink); border-bottom-color: var(--red); }
.proj-tab-year { color: var(--slate); font-weight: 500; font-size: 11.5px; }
.toolbar-top input[type="search"] { flex: 1; min-width: 0; }

/* ---------- cards / list layout toggle ---------- */
.view-toggle { flex-shrink: 0; display: flex; border: 1px solid var(--line); border-radius: 999px; background: var(--mist); overflow: hidden; }
.vt-btn { border: 0; background: transparent; padding: 10px 16px; font: inherit; font-size: 13px; font-weight: 600; color: var(--slate); cursor: pointer; white-space: nowrap; }
.vt-btn.is-on { background: var(--ink); color: var(--on-ink); }
.vt-btn:not(.is-on):hover { color: var(--ink); }

.grid.view-list { display: flex; flex-direction: column; gap: 7px; }
.grid.view-list .card { flex-direction: row; }
.grid.view-list .card-matte { width: 110px; min-width: 110px; aspect-ratio: auto; min-height: 54px; align-self: stretch; }
.grid.view-list .card-body { display: grid; grid-template-columns: minmax(150px, 1fr) auto; align-items: center; column-gap: 14px; row-gap: 2px; padding: 7px 12px; }
.grid.view-list .card-body > * { grid-column: 1; }
/* The 420px cap plus `overflow: visible` (:514) and flex-end meant a rail wider
   than 420px overflowed LEFTWARD out of its own grid column and painted over the
   filename — measured up to 152px of overlap on desktop. No cap: the column sizes
   to the rail instead. */
.grid.view-list .card-actions { grid-column: 2; grid-row: 1 / span 4; margin-top: 0; padding-top: 0; justify-content: flex-end; gap: 6px; }
.grid.view-list .card-notes { display: none; }
.grid.view-list .card-title { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.grid.view-list .type-chip { font-size: 10px; padding: 1px 7px; }
.grid.view-list .tag-row { gap: 4px; }
.grid.view-list .client-added { font-size: 10.5px; }
.grid.view-list .btn-sm { padding: 4px 9px; font-size: 11.5px; }
.grid.view-list .card { position: relative; }
.grid.view-list .card:has(.card-marks) { padding-left: 38px; }
.grid.view-list .project-card .card-body { display: grid; grid-template-columns: 1fr auto; align-items: center; column-gap: 16px; padding: 12px 16px; }
.grid.view-list .project-card .card-title { grid-column: 1; }
.grid.view-list .project-card .card-meta { grid-column: 2; grid-row: 1; justify-self: end; }
.grid.view-list .card-matte { overflow: visible; }
.grid.view-list .card-marks {
  left: -30px; right: auto; top: 50%; transform: translateY(-50%);
  flex-direction: column; gap: 5px;
}
.grid.view-list .mark { width: 22px; height: 22px; opacity: 1; }
.grid.view-list .mark svg { width: 10px; height: 10px; }
.grid.view-list .new-badge { font-size: 9px; padding: 3px 6px; top: 6px; left: 6px; }
.grid.view-list .archived-badge { font-size: 8.5px; padding: 3px 7px; top: 6px; left: 6px; }
@media (max-width: 700px) {
  .grid.view-list .card-matte { width: 90px; min-width: 90px; min-height: 48px; }
  .grid.view-list .card-body { display: flex; flex-direction: column; align-items: flex-start; }
  .grid.view-list .card-actions { justify-content: flex-start; }
  .vt-btn { padding: 9px 12px; }
}
.chips { display: flex; gap: 8px; flex-wrap: wrap; }
.chip {
  border: 1px solid var(--line); background: var(--mist); border-radius: 999px;
  padding: 6px 13px; font-size: 12.5px; font-weight: 600; color: var(--slate);
  transition: border-color .15s ease, color .15s ease;
}
.chip:hover { border-color: var(--line-strong); color: var(--ink); }
.chip.is-on { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }
.chip-fav.is-on { background: var(--ink); border-color: var(--ink); }

/* ---------- strips & folders ---------- */
.strip, .folder, .contact-card { max-width: 1240px; margin: 0 auto; padding: 26px clamp(16px, 4vw, 40px) 0; }
.strip-title, .folder-head h2 { font-size: 15px; font-weight: 800; letter-spacing: .2em; }
.folder-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; border-top: 1px solid var(--line); padding-top: 22px; }
.folder-head h2 { letter-spacing: .04em; font-size: 20px; }
/* The year is neutral by default. It used to be red everywhere it appeared, which
   on the client page meant a red 2026 down all fifteen project rows, and again on
   every card of the projects grid — the same loud accent repeated seventeen times
   on one screen, carrying almost no information when every project shares a year.
   Red is kept for the ONE project you are actually looking at (the page heading
   and the marquee), where it reads as the brand rather than as noise. */
/* 900 is synthesised — Inter is only loaded at 400/500/600 (_top.php:16), so
   the browser was faux-bolding it. 600 is a weight that actually exists, and one
   margin instead of two (this said 10px, the card rule said 6px). */
.folder-year { color: var(--slate); margin-right: .5em; font-weight: 600; }
.marquee-title .folder-year,
.project-head-wrap .folder-head h2 .folder-year { color: var(--red); }
.strip-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.strip-nav { display: flex; gap: 6px; align-items: center; }
.strip-nav .btn:disabled { opacity: .4; cursor: default; border-color: var(--line); }
.strip-row {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(168px, 190px); gap: 12px;
  overflow-x: auto; padding: 12px 2px 8px; scrollbar-width: none;
  overscroll-behavior-x: contain;
}
.strip-row::-webkit-scrollbar { display: none; }
/* compact strip cards */
.strip-row .card-body { padding: 9px 10px 10px; gap: 3px; }
.strip-row .card-title { font-size: 13px; line-height: 1.3; }
.strip-row .card-meta { font-size: 11px; gap: 6px; }
.strip-row .type-chip { font-size: 10px; padding: 1px 6px; }
.strip-row .card-actions { padding-top: 6px; gap: 6px; }
/* Strip cards are a doorway to the project, not to the file itself: one cover link
   sits over the whole card so nothing inside is separately clickable. */
.strip-row .card { position: relative; }
.strip-cover {
  position: absolute; inset: 0; z-index: 5; display: grid; place-items: center;
  text-decoration: none; border-radius: 0;   /* the card's overflow:hidden clips it to the exact inner shape */
  background: var(--scrim-0); opacity: 0; transition: opacity .15s ease, background .15s ease;
}
.strip-cover span {
  font-family: var(--display); font-weight: 800; font-size: 11px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink);
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: 999px;
  padding: 7px 14px; box-shadow: var(--shadow-pop);
}
.strip-row .card:hover .strip-cover, .strip-cover:focus-visible { opacity: 1; background: var(--scrim); }
.strip-row .card:hover .card-art { filter: saturate(.9); }
.strip-row .btn-sm { padding: 5px 9px; font-size: 12px; }
/* .strip-row .fav sizing removed — a leftover from when the star was an emoji
   glyph. mark_glyph() returns an SVG now, so this made pin and star different
   sizes (33x25 vs 23x19). Both take .strip-row .btn-sm above. */
.strip-row .new-badge { font-size: 9.5px; padding: 4px 7px; top: 8px; left: 8px; }
.card-project {
  display: inline-block; font-size: 11px; font-weight: 600; color: var(--slate);
  text-decoration: none; border-bottom: 1px solid var(--line); align-self: flex-start;
}
.card-project:hover { color: var(--ink); border-bottom-color: var(--line-strong); }
.grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 18px; padding-top: 14px;
  /* Cards stretch to match each other; the slack goes to the artwork rather
     than opening a gap inside the card, so uniform never means empty.
     (grid-auto-rows:1fr would equalise rows too, but the folder headings are
     grid children — it gave each one a full card-height row.) */
  align-items: stretch;
}

/* The thumbnail is the card. A strict 16:9 frame sized by the column, and the
   card's height follows from it plus the text beneath — rather than a fixed
   tile height with the artwork stretched to fill whatever was left over.
   Cards still line up: the row stretches to its tallest and the leftover goes
   to the body, with the actions pinned to the bottom. */
.grid:not(.view-list) .card { height: auto; }
.grid:not(.view-list) .card-matte {
  aspect-ratio: 16 / 9; flex: 0 0 auto; min-height: 0; width: 100%;
}
/* two lines each, so the body can't outgrow the tile it lives in */
.grid:not(.view-list) .card-title,
.grid:not(.view-list) .card-notes {
  display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden;
}
.grid:not(.view-list) .card-title { -webkit-line-clamp: 2; }
.grid:not(.view-list) .card-notes { -webkit-line-clamp: 2; }
/* headings are grid children; they must never be stretched like a tile */
.grid > .group-head { align-self: start; }
.grid:not(.view-list) .card-art { width: 100%; height: 100%; object-fit: cover; }
.grid:not(.view-list) .card-art-icon { object-fit: contain; padding: 12px; }
/* The frame is fixed at 16:9, so the only thing that could still make one row
   taller than another is the text. Reserving the block — two title lines, one
   tag line, one action row — makes every card in the grid the same height. */
/* The caption is title, tags and actions — nothing else. Notes were the one
   variable-length thing left and would have made the body outgrow the picture
   it captions; they stay in both list views and in Edit. */
.grid:not(.view-list) .card-notes { display: none; }
.grid:not(.view-list) .tag-row { flex-wrap: nowrap; overflow: hidden; }
.grid:not(.view-list) .card-body { flex: 0 0 auto; height: 126px; }
.grid:not(.view-list) .card-title { min-height: 2.5em; }
/* A 16:9 frame in a 168px column is only 94px tall, so a small tile carries the
   least it can: one title line and the actions. Tags and the date go — they are
   there at M and L, in both list views, and in the filter chips above. */
.grid.size-s:not(.view-list) .card-body { height: 70px; }
.grid.size-s:not(.view-list) .card-title { -webkit-line-clamp: 1; min-height: 0; }
.grid.size-s:not(.view-list) .tag-row { display: none; }
.grid.size-l:not(.view-list) .card-body { height: 130px; }
/* Uniform cards — everything below the tags scrolls inside the card if it overflows */
.card-rest {
  display: flex; flex-direction: column; gap: 5px; flex: 1 1 auto;
  min-height: 0; max-height: 168px; overflow-y: auto; overflow-x: visible;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.card-rest::-webkit-scrollbar { display: none; }
.card-rest .card-actions { margin-top: auto; }
.grid.view-list .card-rest { display: contents; } /* list rows keep their own layout */
/* Card size control — S is the default (set by JS) */
/* The size-S action row needs 120px of content + 30px reserved for the anchored
   ··· + 22px of body padding + 2px border = 174px. At a 165px floor the row
   overflowed and the copy/pin/star column silently stopped lining up — worst at
   iPad widths, where the phone rules do not apply. */
.grid.size-s { grid-template-columns: repeat(auto-fill, minmax(176px, 1fr)); gap: 12px; }
/* Medium is three across, large is two. Kept as min-widths rather than fixed
   counts so a narrow window still steps down instead of squeezing. */
.grid.size-m { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; }
.grid.size-l { grid-template-columns: repeat(auto-fill, minmax(470px, 1fr)); gap: 20px; }
.grid.size-s .card-title, .grid.size-m .card-title { font-size: 13.5px; }
.grid.size-s .btn-sm, .grid.size-m .btn-sm { padding: 5px 9px; font-size: 11.5px; }
.grid.size-s .card-body, .grid.size-m .card-body { padding: 10px 12px 12px; }
.grid.size-s .card-title { font-size: 12.5px; }
.grid.size-s .type-chip { font-size: 10px; padding: 1px 7px; }
/* Sizes carry into list view as row density */
.grid.view-list.size-s .card-matte { width: 84px; min-width: 84px; }
.grid.view-list.size-s .card-body { padding: 5px 10px; }
.grid.view-list.size-s .card-title { font-size: 12.5px; }
.grid.view-list.size-m .card-matte { width: 110px; min-width: 110px; }
.grid.view-list.size-m .card-body { padding: 7px 12px; }
.grid.view-list.size-l .card-matte { width: 150px; min-width: 150px; }
.grid.view-list.size-l .card-body { padding: 11px 14px; }
.grid.view-list.size-l .card-title { font-size: 14.5px; }
/* Compact segmented pills (sort / size) */
.vt-compact .vt-btn { padding: 8px 11px; font-size: 12px; }
.empty { color: var(--slate); padding: 18px 0; }
/* Friendly empty states on the big list surfaces */
section.folder > .empty, section.folder .grid + .empty {
  border: 1px dashed var(--line); border-radius: 14px; padding: 30px 20px;
  display: grid; gap: 12px; justify-items: center; text-align: center;
}
section.folder > .empty::before {
  content: ''; width: 104px; height: 58px; border-radius: 10px;
  background: url('/assets/img/type-project.svg') center / cover; border: 1px solid var(--line);
}
/* Cap the banner-less marquee tile so it doesn't dominate the page */
.marquee-art .card-matte:has(.card-art-icon) { max-height: 280px; max-width: 498px; width: 100%; justify-self: center; }

/* ---------- cards ---------- */
.card { position: relative; background: var(--bg); border: 1px solid var(--line); border-radius: calc(var(--radius) + 2px); overflow: hidden; box-shadow: var(--shadow); display: flex; flex-direction: column; transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease; }
.card:hover { border-color: var(--line-strong); }
.card.project-card:hover,
.grid:not(.view-list) .card:has(.matte-open):hover,
.grid:not(.view-list) .card:has(button[data-embed]):hover {
  transform: translateY(-2px); box-shadow: var(--shadow-lift); border-color: var(--line-strong);
}
/* Strip cards do NOT lift: they are overlaid by .strip-cover, and a 2px shift
   drops the pointer out of the card near its edge, which flickers the overlay
   on and off. The cover itself is the hover affordance here. */
.strip-row .card:has(.matte-open):hover {
  box-shadow: var(--shadow-lift); border-color: var(--line-strong);
}
.grid.view-list .card:has(.matte-open):hover { background: var(--mist); }
.card .card-matte { border-radius: 0; }
.card-body { padding: 9px 11px 10px; display: flex; flex-direction: column; gap: 4px; flex: 1; }
.card-title { font-size: 15.5px; font-weight: 700; margin: 0; font-family: var(--body); }
.card-meta { display: flex; gap: 10px; align-items: center; margin: 0; color: var(--slate); font-size: 12.5px; flex-wrap: wrap; }
.type-chip { border: 1px solid var(--line); background: var(--mist); border-radius: 999px; padding: 2px 8px; font-size: 11px; font-weight: 600; }
.card-notes { margin: 0; font-size: 13px; color: var(--slate); }
.client-added { margin: 0; font-size: 11.5px; color: var(--warn); font-weight: 600; }
.card-actions {
  display: flex; align-items: center; gap: 6px; margin-top: auto; padding-top: 6px;
  flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.card-actions::-webkit-scrollbar { display: none; }
.card-actions > *, .card-actions form { flex-shrink: 0; }
.card-actions .btn { white-space: nowrap; }

.card-sm .card-notes, .card-sm .client-added { display: none; }

/* ---------- project cards ---------- */
.project-card { text-decoration: none; color: inherit; }
/* The card-specific override is gone entirely: it was a second colour AND a
   second margin for the same span. One rule above now governs every year. */
.project-card:hover { border-color: var(--red); }
.matte-link { display: block; }
/* context line: breadcrumb left, the people on the project right */
.proj-topline {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 16px; flex-wrap: wrap; margin: 0 0 10px;
}
.project-head-wrap .crumbs { margin: 0; }
.proj-topline .proj-team { margin: 0; }
.project-head-wrap .folder-head {
  border-top: 0; padding-top: 0;
  align-items: center; flex-wrap: wrap; gap: 12px 18px;
}
.project-head-wrap .folder-head h2 { margin: 0; flex: 1 1 auto; min-width: 0; }

/* ---------- team page ---------- */
/* One measure for every page. There were three — .folder 1240, .admin-wide 1040,
   .team 860 — so walking Members → a member → their project moved the whole column
   sideways twice: at 1920px the heading sat at 570, then 480, then 380. The page
   appeared to shift under you on every click. 1240 is the measure the rest of the
   app already uses (.folder, .toolbar, .strip, .flashes, .project-banner). */
.team { max-width: 1240px; margin: 0 auto; padding: 30px clamp(16px, 4vw, 40px) 0; }
.team-head h1 { font-size: 28px; margin: 0 0 4px; }
.team-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.team-head .muted { margin: 0; }

.invite-panel { border: 1px solid var(--line); border-radius: 16px; background: var(--mist); padding: 18px; margin: 18px 0 8px; }
.invite-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.invite-fields label { display: grid; gap: 5px; font-size: 13px; font-weight: 600; align-content: start; }
.invite-fields .lbl { display: flex; gap: 6px; align-items: baseline; white-space: nowrap; }
.invite-fields .lbl .muted { font-weight: 400; }
.invite-fields input,
.invite-fields select {
  width: 100%; font: inherit; color: var(--ink);
  border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; background: var(--bg);
  height: 42px; box-sizing: border-box;
}
.invite-fields select {
  appearance: none; -webkit-appearance: none; padding-right: 34px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' fill='none' stroke='%236E6E6E' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center;
}
.invite-fields input:focus,
.invite-fields select:focus { border-color: var(--ink); outline: none; }
.invite-legend { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--slate); margin: 16px 0 8px; }
.invite-actions { display: flex; align-items: center; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
.pick-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 6px; }
.pick { display: flex; align-items: center; gap: 9px; padding: 9px 11px; border: 1px solid var(--line);
        border-radius: 10px; background: var(--bg); font-size: 13.5px; cursor: pointer; }
.pick:hover { border-color: var(--line-strong); }
.pick input { accent-color: var(--ink); }
@media (max-width: 620px) { .invite-fields { grid-template-columns: 1fr; } }

.people { list-style: none; margin: 22px 0 0; padding: 0; border-top: 1px solid var(--line); }
.person { border-bottom: 1px solid var(--line); }
.person.is-off { opacity: .68; }
.person-row { display: flex; align-items: center; gap: 14px; padding: 16px 2px; flex-wrap: wrap; }
.avatar { width: 38px; height: 38px; border-radius: 50%; background: var(--fog); color: var(--ink);
          display: grid; place-items: center; font-weight: 700; font-size: 15px; flex-shrink: 0; }
.person-id { min-width: 180px; flex: 1 1 200px; }
.person-name { margin: 0; font-weight: 600; font-size: 15px; }
.person-mail { margin: 0; font-size: 13px; color: var(--slate); overflow-wrap: anywhere; }
/* Was flex: 1 1 auto. On an 860px row that grew harmlessly; at 1240 it would have
   pushed the role pill and the actions apart into the stretched, ragged gap this
   list is meant to avoid. The name takes the slack instead, and meta and actions
   stay together on the right — the same shape the project rows now use. */
.person-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; flex: 0 0 auto; }
.role-pill { border: 1px solid var(--line); background: var(--mist); border-radius: 999px;
             padding: 3px 10px; font-size: 11.5px; font-weight: 700; letter-spacing: .02em; }
.role-manager { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }
.person-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-wrap: wrap; }
.access-panel { border-top: 1px dashed var(--line); padding: 14px 2px 18px; }
@media (max-width: 640px) { .person-actions { margin-left: 0; width: 100%; } }

/* ---------- admin: sections, projects, file list, feed ---------- */
/* .admin-wide was a third measure invented to give the client page more room than
   .team's 860. With .team at 1240 it has nothing left to widen, so it is a no-op
   kept only so the class in the markup still resolves. */
.admin-wide { max-width: 1240px; }
.head-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-top: 38px; }
.section-head:has(#card-form-title) { margin-bottom: 16px; }
button.status-toggle { cursor: pointer; font: inherit; font-family: var(--display); font-weight: 700; font-size: 10.5px; }
button.status-toggle:hover { box-shadow: 0 0 0 2px rgba(0,0,0,.08); }
.section-head .admin-sub { margin: 0; }
.stack-label { display: grid; gap: 5px; font-size: 13px; font-weight: 600; margin-top: 12px; }
.stack-label select, .w-full { width: 100%; border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; background: var(--bg); }
.w-auto { border: 1px solid var(--line); border-radius: 10px; padding: 8px 11px; background: var(--bg); font-size: 13.5px; }
.inline-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--line); }
.seen-line { flex-basis: 100%; }

.proj { border: 1px solid var(--line); border-radius: 14px; margin: 12px 0; overflow: hidden; }
.proj-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; background: var(--mist); }
.proj-id { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.proj-id h3 { margin: 0; font-size: 16px; }
.proj .access-panel { padding: 16px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); background: var(--bg); }

.filelist { list-style: none; margin: 0; padding: 0; }
.fileitem { display: flex; align-items: center; gap: 11px; padding: 8px 4px; border-top: 1px solid var(--line); flex-wrap: wrap; }
.fileitem:first-child { border-top: 0; }
.fileitem .mini-art { width: 46px; flex-shrink: 0; }
.fileitem-id { flex: 1 1 220px; min-width: 0; }
.fileitem-title { margin: 0; font-size: 13.5px; font-weight: 600; display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.fileitem-sub { margin: 3px 0 0; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.fileitem-actions { display: flex; align-items: center; gap: 6px; margin-left: auto; flex-wrap: wrap; }

.more-options { margin-top: 6px; border-top: 1px dashed var(--line); padding-top: 12px; }
.more-options summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--slate); padding: 4px 0; }
.more-options summary:hover { color: var(--ink); }
.more-options[open] summary { margin-bottom: 12px; }
.cardform-grid.tight { margin-top: 14px; }
.check-stack { display: grid; gap: 8px; }

.feed { list-style: none; margin: 20px 0 0; padding: 0; }
.feed-day { font-size: 11px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
            color: var(--slate); margin: 22px 0 6px; }
.feed-day:first-child { margin-top: 0; }
.feed-row { display: flex; gap: 12px; align-items: baseline; padding: 9px 2px; border-top: 1px solid var(--line); font-size: 14px; flex-wrap: wrap; }
.feed-time { color: var(--slate); font-size: 12.5px; flex: 0 0 68px; }
.feed-what { flex: 1 1 260px; }
.feed-obj { color: var(--slate); }
.feed-org { margin-left: auto; }

/* ---------- add-a-link ---------- */
.addlink { border: 1px dashed var(--line); border-radius: var(--radius); padding: 14px; margin-top: 12px; background: var(--mist); }
.addlink-grid { display: grid; grid-template-columns: 1.2fr 1.6fr .8fr 1.2fr auto; gap: 8px; }
.addlink input, .addlink select { border: 1px solid var(--line); border-radius: 10px; padding: 9px 10px; background: var(--bg); min-width: 0; }
.addlink input:focus, .addlink select:focus { border-color: var(--ink); outline: none; }
/* Minimal add-file row: link first, everything flexes and wraps */
.addlink-flex { display: flex; flex-wrap: wrap; gap: 8px; }
.addlink-flex input { flex: 1 1 160px; }
.addlink-flex input[name="url"] { flex: 2.2 1 240px; }
.addlink-flex input[name="title"] { flex: 1.6 1 190px; }
.addlink-flex .btn { flex: 0 0 auto; }
/* Current project banner shown beside its remove option */
.banner-now { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 8px; }
.banner-now img { width: 132px; height: 44px; object-fit: cover; object-position: 50% 50%; border-radius: 8px; border: 1px solid var(--line); }

.addlink-img { margin-top: 8px; }
.file-pick { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 12.5px; font-weight: 600; }
.file-pick-label { color: var(--slate); }
.file-pick .muted { font-weight: 400; }
.file-pick input[type="file"] { flex: 1 1 220px; font-size: 12.5px; font-weight: 400; }
.addlink-more { margin-top: 10px; }
.addlink-more > summary { cursor: pointer; list-style: none; font-size: 12.5px; font-weight: 600; color: var(--slate); }
.addlink-more > summary::-webkit-details-marker { display: none; }
.addlink-more > summary::before { content: '+ '; }
.addlink-more > summary:hover { color: var(--ink); }
.addlink-more[open] > summary { margin-bottom: 8px; }
/* Share panel */
.share-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.share-tools { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; flex: 1 1 auto; }
.share-tools input[type="email"] { flex: 1 1 220px; }
#share-copy { font-size: 16px; line-height: 1; padding: 6px 10px; }
.hint { font-size: 12px; color: var(--slate); margin: 8px 0 0; }
@media (max-width: 860px) { .addlink-grid { grid-template-columns: 1fr; } }

/* ---------- contact ---------- */
.contact-card {
  margin-top: 48px; display: flex; align-items: center; justify-content: space-between; gap: 16px;
  background: var(--mist); border: 1px solid var(--line); border-radius: 16px;
  padding: 24px clamp(16px, 3vw, 32px); flex-wrap: wrap;
}
.contact-card h2 { margin: 0 0 4px; font-size: 19px; }

/* ---------- player modal ---------- */
.player {
  position: fixed; inset: 0; background: rgba(12,12,12,.9); z-index: 90;
  display: grid; place-items: center; padding: 20px;
  /* The height any frame is allowed to reach: the screen, less the 20px gutter
     top and bottom, less the header row above it. Every mode measures against
     this one value, so none of them can push the file past the bottom edge —
     which is what a hand-tuned "100vh - 130px" per mode kept doing.
     dvh, not vh: on a phone vh is the tallest the viewport ever gets, so with the
     URL bar showing, a vh-sized frame hangs off the bottom of the display. */
  /* An estimate, only used to pick a comfortable size. The guarantee is the
     max-height on .player-box below, which needs no constant: the header can wrap
     to two lines on a phone (66px, not 44) and the box still cannot outgrow the
     gutter, because the frame is allowed to shrink inside it. */
  --player-head: 44px;
  --player-max-h: calc(100dvh - 40px - var(--player-head));
}
@supports not (height: 100dvh) {
  .player { --player-max-h: calc(100vh - 40px - var(--player-head)); }
  .player-box { max-height: calc(100vh - 40px); }
}
/* Fills ~80% of the window, while never letting the 16:9 frame overflow the height */
.player-box {
  width: 80vw; max-width: min(80vw, calc(var(--player-max-h) * 16 / 9));
  /* The actual guarantee. Whatever any mode asks for, the box stops at the
     viewport less the 20px gutter top and bottom, and the frame gives up the
     difference — so nothing ever reaches the bottom edge of the screen. */
  display: flex; flex-direction: column;
  max-height: calc(100dvh - 40px);
}
.player-head { flex: 0 0 auto; }
.player-frame { flex: 0 1 auto; min-height: 0; max-height: var(--player-max-h); }
.player-head { display: flex; justify-content: space-between; align-items: center; color: #fff; padding: 0 2px 10px; font-family: var(--display); font-weight: 700; letter-spacing: .06em; }
.player-close { background: none; border: 0; color: #fff; font-size: 20px; padding: 6px 10px; }
.player-tools { display: flex; align-items: center; gap: 12px; }
.player-dl {
  color: #fff; text-decoration: none; font-size: 13px; font-weight: 600;
  border: 1px solid rgba(255,255,255,.35); border-radius: 999px; padding: 6px 14px;
}
.player-dl:hover { border-color: var(--red); }
.player-frame { aspect-ratio: 16 / 9; background: #000; border-radius: 10px; overflow: hidden; }
.player-frame iframe { width: 100%; height: 100%; border: 0; }
/* A direct file plays in the browser's own player. contain, never cover: a clip
   whose real shape disagrees with the frame gets letterboxed rather than cropped. */
.player-frame video { width: 100%; height: 100%; display: block; object-fit: contain; background: #000; }

/* ---------- admin ---------- */
.admin { max-width: 1100px; margin: 0 auto; padding: 30px clamp(16px, 4vw, 40px) 0; }
.admin-head h1 { font-size: 28px; }
.crumbs { font-size: 13px; color: var(--slate); margin: 0 0 6px; }
.admin-sub { font-size: 14px; letter-spacing: .18em; font-weight: 800; margin: 40px 0 12px; text-transform: uppercase; }
.rowform { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 10px 0 16px; }
.rowform input, .rowform select { border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; background: var(--bg); }
.rowform input:focus, .rowform select:focus { border-color: var(--ink); outline: none; }
.rowform-sub { margin: 8px 0; }
.w-year { width: 92px; }
.table { width: 100%; border-collapse: collapse; background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; font-size: 13.5px; }
.table th { text-align: left; font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--slate); padding: 10px 12px; border-bottom: 1px solid var(--line); background: var(--mist); }
.table td { padding: 10px 12px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.table tr:last-child td { border-bottom: 0; }
.row-dim td { background: var(--mist); }
.row-sub td { background: var(--mist); border-top: 0; padding-top: 4px; }
.row-sub .rowform-sub { margin: 2px 0 10px; flex-wrap: wrap; }
.td-thumb { width: 74px; }
.mini-art { width: 64px; aspect-ratio: 16/9; object-fit: cover; border-radius: 5px; background: var(--ink-deep); }
.tag { display: inline-block; border: 1px solid var(--line); background: var(--mist); border-radius: 999px; font-size: 10.5px; font-weight: 700; padding: 2px 8px; margin: 1px 2px; color: var(--slate); }
.tag-hero { color: var(--danger); border-color: var(--hero-line); background: var(--bg); }
.tag-client { color: var(--warn); border-color: var(--st-prog-line); background: var(--bg); }
.tag-dim { }
.folder-admin { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg); padding: 6px 14px 14px; margin: 12px 0; }
.folder-admin summary { cursor: pointer; padding: 10px 0; display: flex; gap: 10px; align-items: baseline; }
.cardform { border: 1px solid var(--line); border-radius: 16px; background: var(--bg); padding: 20px; }
.cardform-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.cardform-grid label { font-size: 13px; font-weight: 600; display: grid; gap: 5px; align-content: start; }
.cardform-grid .lbl { display: flex; gap: 6px; align-items: baseline; white-space: nowrap; }
.cardform-grid .lbl .muted { font-weight: 400; }
.cardform-grid input:not([type=checkbox]), .cardform-grid select { border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; background: var(--mist); width: 100%; }
.cardform-grid input:focus, .cardform-grid select:focus { border-color: var(--ink); outline: none; background: var(--bg); }
.cardform-wide { grid-column: 1 / -1; }
.check { display: flex !important; flex-direction: row !important; align-items: center; gap: 8px; font-weight: 500 !important; }
.check-inline { display: inline-flex !important; margin-top: 6px; }
.cardform-actions { display: flex; gap: 10px; margin-top: 18px; }
@media (max-width: 720px) { .cardform-grid { grid-template-columns: 1fr; } }

/* editing highlight */
.cardform.is-editing { border-color: var(--red); }

/* ---------- mobile app feel ---------- */
* { -webkit-tap-highlight-color: transparent; }
@media (max-width: 640px) {
  .topbar { padding: 10px 14px; padding-top: max(10px, env(safe-area-inset-top)); }
  .topnav { gap: 13px; }
  .topnav a { font-size: 13px; }
  /* comfortable tap targets */
  .btn-sm { padding: 9px 13px; }
  .btn-xs { padding: 8px 11px; }
  /* stop iOS auto-zoom on focus */
  input, select, textarea { font-size: 16px; }
  /* Toolbar stacks: search owns the first row, every control group shares the
     second and spreads evenly across it. The search kept its 420px cap, which
     left just enough room beside it for the sort group to ride up onto row one,
     and `.view-toggle { margin-left: auto }` then shoved whichever group landed
     first on row two into the middle — hence the lopsided gaps. */
  .toolbar-top { flex-wrap: wrap; gap: 10px; justify-content: space-between; }
  .toolbar-top input[type="search"] { flex: 1 1 100%; max-width: none; }
  .toolbar-top .view-toggle, .toolbar-top .sel-toggle { margin-left: 0; }
  .view-toggle { margin-left: auto; }
  .grid { gap: 14px; }
  .section-head { margin-top: 30px; }
  .footer { padding-bottom: calc(18px + env(safe-area-inset-bottom)); }
  .who-btn { max-width: 130px; }
}

/* ---------- image cropper ---------- */
.cropper {
  position: fixed; inset: 0; z-index: 90; display: grid; place-items: center;
  background: rgba(20,20,20,.55); padding: 20px;
}
.cropper-panel {
  background: var(--bg); border: 1px solid var(--line); border-radius: 16px;
  padding: 18px; max-width: 100%; box-shadow: var(--shadow-pop);
}
.cropper-title { font-family: var(--display); font-weight: 800; font-size: 13px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--slate); margin: 0 0 10px; }
.cropper-stage { position: relative; border-radius: 12px; overflow: hidden; background: var(--ink-deep); }
.cropper-canvas { display: block; cursor: grab; touch-action: none; }
.cropper-canvas:active { cursor: grabbing; }
.cropper-guide { position: absolute; inset: 0; pointer-events: none; display: grid; place-items: center; }
.cropper-guide::before {
  content: ''; position: absolute; inset: 0;
  border: 1px solid rgba(255,255,255,.55);
  background:
    linear-gradient(to right, transparent 33.33%, rgba(255,255,255,.28) 33.33%, rgba(255,255,255,.28) calc(33.33% + 1px), transparent calc(33.33% + 1px)),
    linear-gradient(to right, transparent 66.66%, rgba(255,255,255,.28) 66.66%, rgba(255,255,255,.28) calc(66.66% + 1px), transparent calc(66.66% + 1px)),
    linear-gradient(to bottom, transparent 33.33%, rgba(255,255,255,.28) 33.33%, rgba(255,255,255,.28) calc(33.33% + 1px), transparent calc(33.33% + 1px)),
    linear-gradient(to bottom, transparent 66.66%, rgba(255,255,255,.28) 66.66%, rgba(255,255,255,.28) calc(66.66% + 1px), transparent calc(66.66% + 1px));
}
/* The banner window: the strip the project page actually shows, at true shape.
   The huge spread box-shadow dims everything outside it and is clipped by the
   stage's overflow, so the bright area reads as "this is the banner". */
.cropper-band {
  position: absolute; left: 0; width: 100%; top: 0;
  border-top: 1px dashed rgba(255,255,255,.95);
  border-bottom: 1px dashed rgba(255,255,255,.95);
  box-shadow: 0 0 0 2000px rgba(12,12,12,.55);
  cursor: grab; touch-action: none;
}
.cropper-band:active { cursor: grabbing; }
.cropper-band:focus-visible { outline: 2px solid var(--red); outline-offset: -2px; }
.cropper-band-tag {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  font-family: var(--display); font-size: 9.5px; font-weight: 800;
  letter-spacing: .14em; text-transform: uppercase; color: #fff;
  background: rgba(20,20,20,.6); border-radius: 999px; padding: 3px 10px;
  pointer-events: none; white-space: nowrap;
}
/* Repositioning an existing banner leaves the picture alone, so it must not
   invite a drag it will not honour. */
.cropper-canvas.is-fixed { cursor: default; }
.cropper-zoom[hidden] { display: none; }
.cropper-zoom { display: flex; align-items: center; gap: 10px; margin-top: 12px; font-size: 12.5px; font-weight: 600; color: var(--slate); }
.cropper-zoom input { flex: 1; }
.cropper-hint { margin-top: 6px; }
.cropper-actions { display: flex; gap: 8px; margin-top: 12px; }

/* ---------- watch pages listed on a project ---------- */
.watch-list { display: grid; gap: 8px; margin-top: 10px; }
.watch-item {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  border: 1px solid var(--line); border-radius: 12px; padding: 9px 12px; background: var(--bg);
}
.watch-name { font-weight: 700; font-size: 13.5px; }
.watch-link { font-size: 12.5px; color: var(--slate); text-decoration: none; }
.watch-link:hover { color: var(--red); }
.watch-tools { display: flex; align-items: center; gap: 6px; margin-left: auto; flex-wrap: wrap; }
.watch-email {
  border: 1px solid var(--line); border-radius: 999px; padding: 5px 10px;
  font-size: 12px; background: var(--mist); min-width: 170px;
}
.watch-email:focus { border-color: var(--ink); outline: none; background: var(--bg); }

/* ---------- featured file: a soft halo, not a hard border ---------- */
.card.is-featured {
  border-color: var(--feature-line);
  box-shadow: 0 0 0 3px rgba(var(--feature-rgb),.14), 0 6px 22px rgba(var(--feature-rgb),.16);
}
.card.is-featured:hover { box-shadow: 0 0 0 3px rgba(var(--feature-rgb),.22), 0 10px 28px rgba(var(--feature-rgb),.24); }

/* ---------- embedded videos on a project ---------- */
.embeds-section { margin-bottom: 10px; }
.embed-frame {
  position: relative; width: 100%; aspect-ratio: 16 / 9;
  background: #000; border-radius: 14px; overflow: hidden; border: 1px solid var(--line);
}
.embed-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.embed-hero-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 2px 0; font-size: 13px;
}
.embed-hero-title { font-weight: 700; }
.embed-item-tools { display: flex; gap: 6px; margin-left: auto; flex-wrap: wrap; }
.embed-hero-bar .embed-code { margin-left: auto; }
.embed-item-tools .embed-code { margin-left: 0; }

.embed-grid { display: grid; gap: 16px; }
.embed-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.embed-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 1000px) { .embed-grid.cols-3 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .embed-grid.cols-2, .embed-grid.cols-3 { grid-template-columns: 1fr; } }

/* 4+ — hero player with a row of thumbnails beneath */
.embed-strip {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px; margin-top: 14px;
}
.embed-thumb {
  position: relative; display: flex; flex-direction: column; gap: 6px; text-align: left;
  border: 1px solid var(--line); border-radius: 12px; background: var(--bg);
  padding: 6px; cursor: pointer; font: inherit; color: inherit;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.embed-thumb:hover { border-color: var(--slate); }
.embed-thumb.is-on { border-color: var(--ink); box-shadow: 0 0 0 2px rgba(20,20,20,.06); }
.embed-thumb-art {
  display: block; position: relative; aspect-ratio: 16 / 9; border-radius: 8px;
  overflow: hidden; background: var(--mist); display: grid; place-items: center;
}
.embed-thumb-art img { width: 100%; height: 100%; object-fit: cover; }
.embed-thumb-fallback { color: var(--slate); font-size: 18px; }
.embed-thumb-name {
  font-size: 12px; font-weight: 600; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.embed-thumb-x {
  position: absolute; top: 4px; right: 4px; width: 20px; height: 20px; border-radius: 999px;
  background: var(--bg-elev); border: 1px solid var(--line); color: var(--slate);
  display: grid; place-items: center; font-size: 13px; line-height: 1; opacity: 0;
}
.embed-thumb:hover .embed-thumb-x { opacity: 1; }
.embed-thumb-x:hover { color: var(--red); border-color: var(--red); }
/* the featured-videos header behaves like a folder heading, minus the grid placement */
/* the featured-videos header behaves like a folder heading, with room to breathe */
.embeds-head { grid-column: auto; margin-top: 14px; padding-top: 16px; margin-bottom: 10px; }
#embeds-body { margin-top: 2px; }
/* a video already promoted to Featured Videos — a state, not a button */
.is-featured-flag { cursor: default; opacity: .75; }
.is-featured-flag:hover { border-color: var(--line); background: transparent; }

/* ---------- a file whose link no longer works ---------- */
.card.is-broken { border-color: var(--broken-line); }
.card.is-broken .card-matte { background: var(--broken-bg); }
.card.is-broken .card-title { color: var(--broken-ink); }
.broken-badge {
  position: absolute; top: 10px; left: 10px; z-index: 2;
  background: var(--broken); color: #fff; font-family: var(--display); font-weight: 800;
  letter-spacing: .1em; font-size: 9.5px; padding: 5px 9px; border-radius: 999px;
}
/* With no real artwork the icon and its badge are one centred stack, so they
   stay centred at every card size instead of being nudged by a fixed offset. */
.grid:not(.view-list) .card-matte:has(.card-art-icon) {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
}
/* Clickable cards wrap the image in a full-width .matte-open anchor, so the
   centring has to reach through it or the icon just pins to the anchor's left. */
.grid:not(.view-list) .card-matte:has(.card-art-icon) .matte-open {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: auto; flex: 0 0 auto;
}
.grid:not(.view-list) .card-matte:has(.card-art-icon) .card-art-icon {
  /* A 46px glyph floated in a 120px matte read as an afterthought. These are
     the artwork for anything without a thumbnail — transcripts, docs, audio —
     so they get real presence, still centred in the frame. */
  width: auto; height: 66px; flex: 0 0 auto; object-fit: contain; padding: 0;
}
.grid.size-l:not(.view-list) .card-matte:has(.card-art-icon) .card-art-icon { height: 80px; }
.grid:not(.view-list) .card-matte:has(.card-art-icon) .broken-badge,
.grid:not(.view-list) .card-matte:has(.card-art-icon) .review-badge {
  position: static; transform: none; flex: 0 0 auto;
}
/* List rows give the badge a ~84x54 tile, where "BROKEN LINK" wrapped to two
   lines and buried the thumbnail. Swap in the one-word label instead. */
.grid.view-list .broken-badge {
  top: 4px; left: 4px; padding: 2px 6px; font-size: 0; white-space: nowrap; line-height: 1.5;
}
.grid.view-list .broken-badge::after { content: attr(data-short); font-size: 8px; letter-spacing: .08em; }
/* the Broken filter chip stands apart from the ordinary tags */
#tag-chips .chip[data-tag="broken"] { border-color: var(--broken-line); color: var(--broken-ink); }
#tag-chips .chip[data-tag="broken"].is-on { background: var(--broken); border-color: var(--broken); color: #fff; }

/* ---------- watch pages: a slow violet-to-cyan drift ----------
   Violet reads as "premiere" and stays clear of the red a featured file uses.
   The resting box-shadow below is also what reduced-motion viewers keep, since
   the global rule at the top of this file strips the animation. */
.card.is-watch {
  border-color: var(--watch-line);
  box-shadow: 0 0 0 2px rgba(124,92,255,.13), 0 6px 20px rgba(124,92,255,.13);
  animation: watch-glow 5.5s ease-in-out infinite;
}
@keyframes watch-glow {
  0%, 100% { border-color: var(--watch-line-a); box-shadow: 0 0 0 2px rgba(124,92,255,.13), 0 5px 18px rgba(124,92,255,.12); }
  50%      { border-color: var(--watch-line-b); box-shadow: 0 0 0 3px rgba(34,199,255,.22), 0 10px 26px rgba(34,199,255,.17); }
}
/* roomier cards read as calmer, denser ones as busier — even out the tempo */
.grid.view-list .card.is-watch { animation-duration: 7s; }
.card-sm.is-watch { animation-duration: 6.5s; }

/* The Watch Page tag keeps its tinted chip. The Open button only takes the
   colour — inside a card the actions are words, not boxes, so the border and
   fill that made sense on a chip made it the one outlined button on the page. */
.type-chip.chip-watch {
  border-color: var(--watch-edge); color: var(--watch-ink);
  background: linear-gradient(100deg, var(--watch-chip-a), var(--watch-chip-b));
}
.card.is-watch .card-actions .btn-primary {
  color: var(--watch-ink); background: none; border-color: transparent;
}
.card.is-watch .card-actions .btn-primary:hover { color: var(--watch-ink-hover); background: none; border-color: transparent; }
/* list rows still show buttons as buttons, so keep the tint there */
.grid.view-list .card.is-watch .card-actions .btn-primary {
  border-color: var(--watch-edge);
  background: linear-gradient(100deg, var(--watch-chip-a), var(--watch-chip-b));
}

/* ---------- dark mode: assets and the theme switch ----------
   The flat SVG icon set is baked light (a #F6F6F5 field, #FFFFFF tile, #6E6E6E
   glyph), so rather than shipping 24 duplicate files it is inverted. The
   hue-rotate keeps the two red-tinted "broken" icons red instead of flipping
   them cyan. Photographic thumbnails are deliberately NOT touched. */
.card-art-icon, .type-badge, .mini-art.is-icon { filter: var(--icon-flip); }
/* the wordmark has a real white-ink cut, so use it rather than inverting.
   A member's own logo must be exempt — swapping in our white mark here would put
   the Bowie Media wordmark on a white-labelled page the moment it went dark. */
:root[data-theme="dark"] .brand-mark:not(.brand-mark-custom),
:root[data-theme="dark"] .auth-logo { content: url('/assets/img/bm-icon-trans-white.png'); }

/* theme switch, in the account menu */
.who-theme {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  width: 100%; border-top: 1px solid var(--line);
}
.who-switch {
  flex: none; position: relative; width: 32px; height: 18px; border-radius: 999px;
  background: var(--fog); border: 1px solid var(--line); transition: background .16s ease, border-color .16s ease;
}
.who-switch::after {
  content: ''; position: absolute; top: 1px; left: 1px; width: 14px; height: 14px;
  border-radius: 50%; background: var(--bg-elev); box-shadow: 0 1px 2px rgba(0,0,0,.28);
  transition: transform .16s ease;
}
.who-theme[aria-pressed="true"] .who-switch { background: var(--ink); border-color: var(--ink); }
.who-theme[aria-pressed="true"] .who-switch::after { transform: translateX(14px); }

/* armed state for any [data-confirm] button — matches the folder-delete chip */
.btn.confirming, button.confirming:not(.gh-del) {
  background: var(--red); border-color: var(--red); color: #fff; font-weight: 700;
}
.btn.confirming:hover { border-color: var(--red); }

/* ---------- project header + toolbar: tighter vertical rhythm ----------
   The stack above the first file ran ~456px. Section chrome was the cost,
   not the content, so the padding comes down rather than anything being cut. */
.project-head-wrap { padding-top: 16px; }
.toolbar { gap: 9px; }
.toolbar-top input[type="search"] { padding-top: 8px; padding-bottom: 8px; }
.folder .grid { padding-top: 8px; }
/* the first group heading has the toolbar's rule above it already */
.grid > .group-head:first-child { margin-top: 4px; padding-top: 8px; border-top: 0; }

/* ---------- review links (frame.io, Wipster, SyncSketch) ----------
   These open somebody else's commenting workspace rather than a finished file,
   so they get their own amber identity — distinct from the red of a broken link
   and the blue of the featured file. */
.card.is-review { border-color: var(--review-line); }
.card.is-review .card-matte { background: var(--review-bg); }
.card.is-review .card-title { color: var(--review-ink); }
.review-badge {
  position: absolute; top: 10px; left: 10px; z-index: 2;
  background: var(--review); color: #fff; font-family: var(--display); font-weight: 800;
  letter-spacing: .1em; font-size: 9.5px; padding: 5px 9px; border-radius: 999px;
}
.grid.view-list .review-badge {
  top: 4px; left: 4px; padding: 2px 6px; font-size: 0; white-space: nowrap; line-height: 1.5;
}
.grid.view-list .review-badge::after { content: attr(data-short); font-size: 8px; letter-spacing: .08em; }
.type-chip.chip-review { border-color: var(--review-line); color: var(--review-ink); background: var(--review-bg); }
/* and the filter chip for the Review tag */
#tag-chips .chip[data-tag="review"] { border-color: var(--review-line); color: var(--review-ink); }
#tag-chips .chip[data-tag="review"].is-on { background: var(--review); border-color: var(--review); color: #fff; }

/* password controls on the share panel */
.share-pass-row { margin-top: 10px; gap: 8px; flex-wrap: wrap; }
.share-pass-row input[type="text"] { flex: 1; min-width: 180px; }
.share-locked { font-size: 12px; font-weight: 600; color: var(--ok); white-space: nowrap; }
/* the gate page */
.gate-mark { width: 40px; height: 40px; display: block; margin: 0 0 14px; }
.gate-mark-custom { width: auto; height: 38px; max-width: 220px; object-fit: contain; }

/* Admin: the current logo sits on a chequer so a transparent PNG reads as
   transparent rather than as a white rectangle that happens to match the card. */
.logo-current { display: flex; align-items: center; gap: 12px; margin: 0 0 10px; }
.logo-preview {
  height: 44px; width: auto; max-width: 220px; object-fit: contain;
  padding: 6px 10px; border-radius: 8px; border: 1px solid var(--line);
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #e9e9e9 25%, transparent 25%, transparent 75%, #e9e9e9 75%),
    linear-gradient(45deg, #e9e9e9 25%, transparent 25%, transparent 75%, #e9e9e9 75%);
  background-size: 14px 14px;
  background-position: 0 0, 7px 7px;
}
.gate-go { margin-top: 14px; width: 100%; }

/* ---------- embedded players ----------
   9. Don't force our dark colour-scheme onto a third-party (light) document —
   the browser paints a light canvas for it, which bled through the frame's
   rounded corners as a white edge. Pin the iframe's own backdrop black too. */
.player-frame iframe, .embed-frame iframe {
  color-scheme: light;
  background: #000;
  display: block;
}

/* 2. Phone: the lightbox chrome had desktop padding and wrapped awkwardly. */
@media (max-width: 620px) {
  .player { padding: 10px; }
  .player-head { padding: 0 2px 8px; gap: 8px; }
  .player-tools { gap: 8px; }
  .player-dl { font-size: 12px; padding: 5px 10px; white-space: nowrap; }
  .player-title { font-size: 12px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .player-close { font-size: 18px; padding: 4px 8px; }
}

/* theme switch on public share pages */
.topbar-right { display: flex; align-items: center; gap: 12px; }
.theme-btn {
  display: inline-flex; align-items: center; background: none; border: 0;
  padding: 4px; cursor: pointer; line-height: 0;
}
.theme-btn[aria-pressed="true"] .who-switch { background: var(--ink); border-color: var(--ink); }
.theme-btn[aria-pressed="true"] .who-switch::after { transform: translateX(14px); }
.theme-btn { gap: 9px; }
.theme-btn .theme-label { white-space: nowrap; }

/* ---------- file preview lightbox ----------
   Documents are portrait-ish, so the doc variant drops the 16:9 box and fills
   the height instead. Everything else is shared with the video player. */
.player--doc .player-box { width: min(94vw, 1080px); max-width: none; }
.player--doc .player-frame { aspect-ratio: auto; height: var(--player-max-h); background: var(--ink-deep); }
.player--doc .player-frame iframe { width: 100%; height: 100%; }
@media (max-width: 620px) {
  .player--doc .player-box { width: 100%; }
}

/* custom domain assignment, on the member's settings panel */
.domain-box { border-top: 1px solid var(--line); margin-top: 14px; padding-top: 14px; }
.domain-title { font-family: var(--display); font-weight: 800; font-size: 12px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--slate); margin: 0 0 8px; }
.domain-list { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 6px; }
.domain-list li { display: flex; align-items: center; gap: 10px; font-size: 13.5px; font-weight: 600; }
.domain-add { display: flex; gap: 8px; flex-wrap: wrap; }
.domain-add input { flex: 1; min-width: 200px; }

/* ---------- list rows keep one height ----------
   .card-matte is align-self:stretch, so anything that makes one row taller —
   a title or meta line that wraps — also blows up that row's thumbnail, which
   reads as "why is this one bigger". Keep the meta on one line and cap the
   thumbnail so a row can never tower over its neighbours. */
.grid.view-list .project-card .card-meta { white-space: nowrap; }
.grid.view-list .project-card .card-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.grid.view-list .card-matte { max-height: 76px; }
@media (max-width: 620px) {
  /* stacked layout on phones legitimately needs the extra room */
  .grid.view-list .card-matte { max-height: none; }
}

/* Rows in the member and project lists use the same chip; it only needs to be
   parked against the right edge and stay centred while it scales on hover. */
.mark.client-pin { position: absolute; top: 50%; right: 6px; transform: translateY(-50%); }
.mark.client-pin:hover { transform: translateY(-50%) scale(1.08); }
.client-item.is-pinned { background: var(--mist); }


/* 4. The Recently opened strip sat inside the narrow .team column. Let it run
   the full list width so it lines up with the rows beneath it. */
/* A strip nested inside .team inherits that column's padding already; .strip's
   own max-width + horizontal padding stacked on top and inset it from the list. */
.team > .strip {
  max-width: none; margin: 4px 0 2px;
  padding-left: 0; padding-right: 0; padding-top: 10px;
}
.team > .strip .strip-row { padding-left: 0; padding-right: 0; }

/* 2. sort + search live in the header row now */
.head-tools-right { margin: 0; justify-content: flex-end; flex: 1 1 320px; }
@media (max-width: 620px) { .head-tools-right { flex-basis: 100%; justify-content: flex-start; } }

/* activity search + pager */
.activity-search {
  border: 1px solid var(--line); border-radius: 999px; padding: 8px 14px;
  background: var(--mist); font-size: 14px; min-width: 200px;
}
.activity-search:focus { background: var(--bg); border-color: var(--ink); outline: none; }
.pager { display: flex; align-items: center; justify-content: center; gap: 14px; margin: 20px 0 8px; }
.pager-at { font-size: 13px; color: var(--slate); font-weight: 600; }
.pager .btn.is-off { opacity: .4; pointer-events: none; }

/* ---------- vertical clips ----------
   Reels share the 16:9 frame so the grid stays regular; the portrait art
   cover-fills it and the play badge marks it as a clip.
   All of the below is CARD view only — list rows keep their normal horizontal
   layout further down so a reel row looks like every other row. */
/* A reel renders exactly like every other card: same 16:9 frame, same inline
   tags, same title size, same action row. The only thing left that is specific
   to it is the play badge on the artwork, which says it is a clip. */
.grid:not(.view-list) .card.is-vertical { position: relative; }
.grid:not(.view-list) .card.is-vertical .card-art { width: 100%; height: 100%; object-fit: cover; }
/* The reel's flat, borderless buttons are now the treatment for every card —
   see the .card-actions rules in the card block. */

.play-badge {
  position: absolute; inset: 0; margin: auto; z-index: 2;
  width: 46px; height: 46px; border-radius: 999px; pointer-events: none;
  background: rgba(20,20,20,.55); border: 1px solid rgba(255,255,255,.5);
}
.play-badge::after {
  content: ''; position: absolute; top: 50%; left: 52%; transform: translate(-50%,-50%);
  border-style: solid; border-width: 9px 0 9px 15px;
  border-color: transparent transparent transparent #fff;
}
.card:hover .play-badge { background: rgba(20,20,20,.58); }

/* ---- list view: a reel is just another row ----
   Same matte width, same body grid, same action rail as every neighbouring row.
/* A reel is an ordinary row in list view too — the only thing it needs is a
   cover-crop so its portrait art fills the same landscape tile. Everything
   else it inherits, so the row layout can never drift from its neighbours. */
.grid.view-list .card.is-vertical .card-art { width: 100%; height: 100%; object-fit: cover; }
.grid.size-s:not(.view-list) .play-badge { width: 34px; height: 34px; }
.grid.size-s:not(.view-list) .play-badge::after { border-width: 7px 0 7px 11px; }
.grid.size-s:not(.view-list) .card-matte:has(.card-art-icon) .card-art-icon { height: 46px; }
.grid.size-s:not(.view-list) .card-matte:has(.card-art-icon) { gap: 7px; }
.grid.view-list .play-badge { width: 26px; height: 26px; }
.grid.view-list .play-badge::after { border-width: 5px 0 5px 8px; }
.strip-row .card.is-vertical .card-matte { aspect-ratio: 9 / 16; }

/* the lightbox goes portrait too */
.player--vertical .player-box { width: auto; max-width: none; }
/* 9:16, and never taller than the room actually available. The old rule set an
   explicit height on desktop and switched to width:100% on phones — on a short
   screen that made a frame taller than the window, so the bottom of the video ran
   off the bottom of the display. Height is now capped by the viewport and the
   width follows from the ratio, which is the same behaviour at every size, so the
   phone override is gone rather than fixed. */
.player--vertical .player-frame {
  aspect-ratio: 9 / 16;
  width: auto; max-width: 100%;
  height: var(--player-max-h); max-height: var(--player-max-h);
  margin: 0 auto;
}
.player--vertical .player-frame iframe { width: 100%; height: 100%; }

/* ---------- batch select (Files page) ----------
   The grid becomes a picker: a checkbox rides each card and a sticky bar
   collects the one action to apply to everything ticked. */
.sel-toggle.is-on { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }

.grid.is-selecting .card { position: relative; cursor: pointer; user-select: none; }
.grid.is-selecting .card .card-actions,
.grid.is-selecting .card .card-marks,
.grid.is-selecting .card .move-select { pointer-events: none; opacity: .45; }
/* In the clean list the actions float over the row, so dimming them just paints
   them across the tags — while picking files they go away entirely. */
.grid.view-rows.is-selecting .card-actions { display: none; }
.grid.is-selecting .card.is-picked {
  border-color: var(--red);
  box-shadow: 0 0 0 2px rgba(var(--red-rgb), .32), var(--shadow);
}
/* The boxes are injected once and kept for reuse, so their visibility has to
   hang off select mode — otherwise they stayed on screen after Cancel. */
.sel-box { display: none; }
.grid.is-selecting .sel-box { display: block; }
.sel-box {
  position: absolute; top: 8px; left: 8px; z-index: 6;
  width: 22px; height: 22px; padding: 0; border-radius: 6px; cursor: pointer;
  border: 1.5px solid var(--line-strong); background: var(--bg);
  box-shadow: 0 1px 4px rgba(0,0,0,.18);
  transition: background .12s ease, border-color .12s ease;
}
.sel-box::after {
  content: ''; position: absolute; top: 3px; left: 7px;
  width: 5px; height: 10px; opacity: 0;
  border: solid var(--on-ink); border-width: 0 2px 2px 0; transform: rotate(42deg);
}
.card.is-picked .sel-box { background: var(--red); border-color: var(--red); }
.card.is-picked .sel-box::after { opacity: 1; }
.grid.view-list.is-selecting .card { padding-left: 66px; }
.grid.view-list.is-selecting .sel-box { top: 50%; margin-top: -11px; left: 10px; }
.grid.view-list.is-selecting .card-marks { left: 38px; }

.bulk-bar {
  position: fixed; z-index: 60; left: 50%; transform: translateX(-50%);
  bottom: max(14px, env(safe-area-inset-bottom));
  max-width: calc(100vw - 24px);
  /* one row at every width — anything needing an input hides in a popover, and
     the row itself scrolls sideways rather than wrapping to a second line */
  display: flex; align-items: center; flex-wrap: nowrap; gap: 6px;
  padding: 8px 10px; border-radius: 999px;
  background: var(--bg-elev); border: 1px solid var(--line-strong);
  box-shadow: 0 12px 34px rgba(0,0,0,.22);
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.bulk-bar::-webkit-scrollbar { display: none; }
.bulk-bar > * { flex: 0 0 auto; }
.bulk-count { font-size: 12px; color: var(--slate); white-space: nowrap; padding: 0 4px 0 6px; }
.bulk-count strong { color: var(--ink); font-size: 14px; }
.bulk-sep { width: 1px; height: 20px; background: var(--line); margin: 0 2px; }
.bulk-end { display: flex; gap: 4px; padding-left: 2px; }
.bulk-bar .btn { border-radius: 999px; white-space: nowrap; }
.bulk-bar .btn[disabled] { opacity: .4; pointer-events: none; }
body.bulk-open .footer { padding-bottom: 74px; }

/* Any control that needs typing lives in a popover above the bar. */
.bulk-pop-btn.is-open, .bulk-pop-btn.has-picks {
  background: var(--ink); border-color: var(--ink); color: var(--on-ink);
}
.bulk-pop-menu {
  position: absolute; bottom: calc(100% + 10px); left: 0; z-index: 5;
  display: flex; flex-direction: column; gap: 7px;
  width: max(220px, 100%); padding: 10px; border-radius: 12px;
  background: var(--bg-elev); border: 1px solid var(--line-strong);
  box-shadow: 0 12px 30px rgba(0,0,0,.22);
}
.bulk-pop-wide { width: 320px; }
.bulk-pop-menu input, .bulk-pop-menu select, .bulk-pop-menu textarea {
  width: 100%; font: inherit; font-size: 12.5px; padding: 6px 9px;
  border-radius: 8px; border: 1px solid var(--line);
  background: var(--bg); color: var(--ink);
}
.bulk-pop-menu textarea { resize: vertical; min-height: 44px; }
.bulk-pop-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.bulk-pop-row .hint { font-size: 10.5px; color: var(--slate); flex: 1 1 120px; margin: 0; }
@media (max-width: 720px) {
  .bulk-bar { padding: 7px 8px; gap: 5px; }
  .bulk-pop-menu, .bulk-pop-wide { width: min(280px, calc(100vw - 40px)); }
  /* keep the popover on screen when its trigger is near the right edge */
  .bulk-pop:nth-last-child(-n+5) .bulk-pop-menu { left: auto; right: 0; }
  .bulk-end { margin-left: 0; }
}

/* multi-project destination picker inside the bulk bar */
.proj-pick-btn.has-picks { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }
.proj-pick-n {
  display: inline-block; margin-left: 6px; min-width: 16px; padding: 0 4px;
  border-radius: 999px; background: var(--red); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
}
.proj-pick-menu { width: min(320px, calc(100vw - 40px)); }
.proj-pick-list { max-height: 230px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.proj-pick-row {
  display: flex; align-items: center; gap: 8px; margin: 0;
  padding: 5px 7px; border-radius: 7px; font-size: 12.5px; color: var(--ink); cursor: pointer;
}
.proj-pick-row:hover { background: var(--mist); }
.proj-pick-row input { accent-color: var(--red); margin: 0; }
.proj-pick-row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Archives: Restore is the whole point of the row, so it never hides on hover */
.arch-restore { position: absolute; top: calc(50% - 17px); right: 20px; margin: 0; }
.arch-restore .btn { background: var(--bg-elev); }

/* ---------- clean list ("rows") ----------
   The quietest possible way to look at a lot of files: one hairline-separated
   line each, no artwork, no chrome. Tags and date sit right-aligned in muted
   text, and the actions only appear on the row you're pointing at. */
.grid.view-rows { gap: 0; border-top: 1px solid var(--line); }
.grid.view-rows .card {
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
  box-shadow: none; background: none; padding-left: 0;
  transition: background .12s ease;
}
.grid.view-rows .card:hover { background: var(--mist); }
.grid.view-rows .card-matte,
.grid.view-rows .type-badge,
.grid.view-rows .card-notes,
.grid.view-rows .card-project,
.grid.view-rows .broken-badge,
.grid.view-rows .archived-badge,
.grid.view-rows .new-badge,
.grid.view-rows .play-badge { display: none; }
/* every row indents the same whether or not it has pin/star marks */
.grid.view-rows .card, .grid.view-rows .card:has(.card-marks) { padding-left: 34px; position: relative; }
.grid.view-rows .card-marks { left: 8px; }
/* "Added by …" is a second line, and a second line is what makes a list untidy */
.grid.view-rows .client-added { display: none; }
.grid.view-rows .card-body {
  display: grid; grid-template-columns: 17px minmax(0, 1fr) auto;
  align-items: center; column-gap: 10px; row-gap: 0;
  padding: 9px 12px 9px 4px; min-height: 38px;
}
.row-icon { display: none; }
.grid.view-rows .row-icon {
  display: block; grid-column: 1; grid-row: 1;
  width: 17px; height: 17px; border-radius: 4px; object-fit: contain;
}

/* The type icon sits beside the title rather than floating on the artwork —
   the same place the clean list puts it, and it leaves the thumbnail clean. */
.grid:not(.view-list) .type-badge { display: none; }
.grid:not(.view-list) .card-body {
  display: grid; grid-template-columns: 16px minmax(0, 1fr);
  align-items: start; column-gap: 7px; row-gap: 4px;
}
.grid:not(.view-list) .row-icon {
  display: block; grid-column: 1; grid-row: 1;
  width: 16px; height: 16px; margin-top: 1px; border-radius: 4px; object-fit: contain;
}
.grid:not(.view-list) .card-title { grid-column: 2; grid-row: 1; }
.grid:not(.view-list) .card-project,
.grid:not(.view-list) .card-meta,
.grid:not(.view-list) .tag-row { grid-column: 2; }
/* The buttons use the card's full width — sharing the title's column left them
   indented by the icon gutter, which is what pushed them out of frame. */
.grid:not(.view-list) .card-rest { grid-column: 1 / -1; }
/* the reel puts its tags and actions on the frame, so they leave the grid flow */
.grid:not(.view-list) .card.is-vertical .card-rest { grid-column: 1 / -1; }
.grid.view-rows .card-title {
  grid-column: 2; grid-row: 1; font-size: 13.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.grid.view-rows .tag-row {
  grid-column: 3; grid-row: 1; flex-wrap: nowrap; gap: 8px;
  justify-content: flex-end; font-size: 11px; color: var(--slate);
}
/* tags read as plain words here — the pill chrome is what makes a list noisy */
.grid.view-rows .type-chip {
  background: none; border: 0; padding: 0; color: var(--slate);
  font-size: 11px; font-weight: 500; letter-spacing: .01em;
}
.grid.view-rows .type-chip + .type-chip::before { content: '· '; color: var(--line-strong); }
.grid.view-rows .card-rest { display: contents; }
/* Actions float over the row instead of holding a column open — a reserved
   column would be a different width on every row and make the tags ragged. */
.grid.view-rows .card-actions {
  /* No transform here: a transformed ancestor becomes the containing block for
     position:fixed, which threw the overflow menu clean off the viewport. */
  position: absolute; right: 10px; top: 0; bottom: 0; align-items: center;
  grid-column: auto; justify-content: flex-end; gap: 4px;
  margin: 0; padding: 0 0 0 20px; max-width: none;
  background: linear-gradient(to right, transparent, var(--mist) 20px);
  opacity: 0; pointer-events: none; transition: opacity .12s ease;
}
.grid.view-rows .card:hover .card-actions,
.grid.view-rows .card:focus-within .card-actions { pointer-events: auto; }
.grid.view-rows .card:hover .card-actions,
.grid.view-rows .card:focus-within .card-actions { opacity: 1; }
.grid.view-rows .card-actions .btn { font-size: 11px; padding: 3px 8px; }
.grid.view-rows .move-select { font-size: 11px; padding: 3px 7px; }
.grid.view-rows .group-head { border: 0; margin-top: 18px; }
/* a reel is just a line of text here like everything else */
.grid.view-rows .card.is-vertical .card-matte { display: none; }
.grid.view-rows.is-selecting .card { padding-left: 40px; }
.grid.view-rows.is-selecting .sel-box { top: 50%; margin-top: -11px; left: 6px; }
.grid.view-rows.is-selecting .card-marks { left: 40px; }
@media (max-width: 700px) {
  .grid.view-rows .tag-row { display: none; }
  .grid.view-rows .card-actions { opacity: 1; }
}

/* ---------- public links panel ----------
   Addresses that already work without signing in. They're shown wherever they
   apply — a project and its member page — because these are the links clients
   actually hold, and a link you can't see is a link you can't revoke. */
.pub-links {
  margin: 14px 0 4px; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg-elev);
}
.pub-links-head {
  display: flex; align-items: center; gap: 8px; margin: 0 0 8px;
  font-size: 11px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: var(--slate);
}
.pub-links-head svg { color: var(--red); flex: 0 0 auto; }
.pub-links-head .muted { margin-left: auto; letter-spacing: 0; text-transform: none; font-weight: 500; }
.pub-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
.pub-item {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 6px; border-radius: 8px;
}
.pub-item:hover { background: var(--mist); }
.pub-kind {
  flex: 0 0 auto; min-width: 74px;
  font-size: 9.5px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase;
  color: var(--slate);
}
.pub-watch .pub-kind { color: var(--red); }
.pub-main { display: flex; align-items: baseline; gap: 10px; min-width: 0; flex: 1 1 auto; }
.pub-label {
  font-size: 13px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pub-lock { font-size: 10px; }
.pub-url {
  font-size: 11.5px; color: var(--slate); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pub-url:hover { color: var(--red); text-decoration: underline; }
.pub-tools { display: flex; gap: 4px; margin-left: auto; flex: 0 0 auto; }
@media (max-width: 640px) {
  .pub-item { flex-wrap: wrap; }
  .pub-main { flex-direction: column; gap: 1px; width: 100%; }
  .pub-tools { margin-left: 0; }
}

/* Icon pills keep the exact metrics of the S / M / L pills beside them — only
   the centring differs, since an SVG has no text baseline to sit on. */
.view-toggle .vt-btn:has(svg) { display: inline-flex; align-items: center; justify-content: center; }
/* An SVG box is shorter than a text line box, which left the icon pills 4px
   under the S/M/L pills. The box matches the line height; the art stays 15px. */
.view-toggle .vt-btn svg { display: block; box-sizing: content-box; padding: 2px 0; }

/* ---------- file edit dialog (Files / Archives) ---------- */
.edit-scrim { position: fixed; inset: 0; z-index: 70; background: var(--scrim); }
.edit-modal {
  position: fixed; z-index: 71; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(560px, calc(100vw - 28px)); max-height: min(86vh, 760px);
  display: flex; flex-direction: column;
  border-radius: 16px; border: 1px solid var(--line-strong); background: var(--bg-elev);
  box-shadow: 0 24px 60px rgba(0,0,0,.32);
}
.edit-head {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; border-bottom: 1px solid var(--line);
}
.edit-head h2 {
  margin: 0; font-size: 15px; font-weight: 700; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.edit-head .btn { margin-left: auto; }
.edit-body {
  padding: 14px 16px; overflow-y: auto;
  display: flex; flex-direction: column; gap: 11px;
}
.edit-body label { display: flex; flex-direction: column; gap: 4px; margin: 0; }
.edit-body .lbl { font-size: 11.5px; font-weight: 600; color: var(--slate); }
.edit-body input[type="text"], .edit-body input:not([type]) {
  font-size: 13px; padding: 8px 10px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
}
.edit-body label.check { flex-direction: row; align-items: center; gap: 8px; font-size: 12.5px; }
.edit-foot {
  display: flex; gap: 8px; padding: 12px 16px;
  border-top: 1px solid var(--line);
}
body.modal-open { overflow: hidden; }

/* CC — a caption track hanging off a video card */
.cc-btn {
  font-family: var(--display); font-weight: 800; letter-spacing: .06em;
  font-size: 10.5px; padding: 5px 8px;
}

/* drag-to-reorder: a line where the card will land */
.drop-before, .drop-after { position: relative; }
.drop-before::before, .drop-after::after {
  content: ''; position: absolute; z-index: 6; border-radius: 3px;
  background: var(--red); box-shadow: 0 0 0 2px rgba(var(--red-rgb), .22);
}
/* card grid: a vertical rule down the side the card will drop on */
.grid:not(.view-list) .drop-before::before { inset: -4px auto -4px -7px; width: 3px; }
.grid:not(.view-list) .drop-after::after   { inset: -4px -7px -4px auto; width: 3px; }
/* list rows: a horizontal rule above or below */
.grid.view-list .drop-before::before { inset: -4px 0 auto 0; height: 3px; }
.grid.view-list .drop-after::after   { inset: auto 0 -4px 0; height: 3px; }
.card.dragging { opacity: .45; }
body.is-dragging .card { cursor: grabbing; }

/* copy confirmation — a state, so the icon survives it */
.copy.is-copied { color: var(--ok, #2E7D52); border-color: currentColor; }
.copy.is-copied svg { display: none; }
.copy.is-copied .copy-word { display: none; }
.copy-pass.is-copied .copy-word, .copy-pass.is-failed .copy-word { display: none; }
.copy.is-copied::after { content: 'Copied ✓'; font-size: 11.5px; font-weight: 600; white-space: nowrap; }
.grid:not(.view-list) .copy.is-copied { width: auto; padding: 0 8px; }

/* ---------- perceived speed ---------- */
.nav-bar {
  position: fixed; top: 0; left: 0; z-index: 200; height: 2px; width: 0;
  background: var(--red); border-radius: 0 2px 2px 0;
  box-shadow: 0 0 8px rgba(var(--red-rgb), .55);
  transition: width 6s cubic-bezier(.05,.7,.1,1);
}
.nav-bar.go { width: 92%; }

/* Thumbnails resolve in rather than snapping. Gated on .js-fade, which the
   script sets — with no JS the images are plainly visible, never hidden. */
.js-fade img.card-art { opacity: 0; transition: opacity .22s ease; }
.js-fade img.card-art.is-loaded { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .js-fade img.card-art { opacity: 1; } }



/* the overlay's own close control */
.menu-close {
  position: absolute; top: 6px; right: 8px; z-index: 2;
  width: 26px; height: 26px; padding: 0; line-height: 1;
  display: grid; place-items: center;
  border: 0; border-radius: 999px; cursor: pointer;
  background: rgba(255,255,255,.14); color: rgba(255,255,255,.9);
  font-size: 17px; font-family: var(--body);
}
.menu-close:hover { background: rgba(255,255,255,.26); color: #fff; }
/* the backdrop is a click target too — cursor says so */
.card-more-menu { cursor: pointer; }
.card-more-menu .btn, .card-more-menu select, .card-more-menu form { cursor: auto; }
.card-more-menu .btn { cursor: pointer; }

/* ==========================================================================
   Mobile
   The topbar was the one thing forcing the whole page sideways: five nav links
   plus the account button came to 446px against a 375px viewport. On a phone
   the bar becomes two rows — brand and account on top, the links scrolling
   underneath — so nothing is cut off and the page itself never scrolls sideways.
   ========================================================================== */
/* the hamburger only exists on phones */
.nav-toggle { display: none; }
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
  display: block; width: 18px; height: 2px; border-radius: 2px;
  background: var(--ink); transition: transform .18s ease, opacity .12s ease;
}
.nav-toggle-bars::before, .nav-toggle-bars::after { content: ''; position: absolute; }
.nav-toggle-bars::before { transform: translateY(-6px); }
.nav-toggle-bars::after  { transform: translateY(6px); }
/* open: the bars become an X */
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before { transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after  { transform: rotate(-45deg); }

@media (max-width: 760px) {
  .topbar { flex-wrap: wrap; row-gap: 0; padding: 10px 14px; gap: 8px; }
  .topbar > .who-menu { margin-left: auto; margin-right: 0; }
  .topbar > .brand { order: 1; }
  .topbar > .who-menu { order: 2; }
  .nav-toggle {
    order: 3; display: inline-flex; align-items: center; justify-content: center;
    position: relative; width: 38px; height: 38px; padding: 0;
    border: 1px solid var(--line); border-radius: 10px;
    background: var(--bg); cursor: pointer;
  }
  .nav-toggle:hover { background: var(--mist); }

  /* Collapsed by default; when open it drops below as a full-width panel with
     the links stacked and centred rather than running left to right. */
  .topnav {
    order: 4; width: 100%; margin-left: 0; display: none;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: 6px 0 4px; margin-top: 8px; border-top: 1px solid var(--line);
  }
  .topnav.is-open { display: flex; }
  .topnav a {
    display: block; width: 100%; text-align: center;
    padding: 13px 0; font-size: 15px; border-radius: 10px;
  }
  .topnav a:hover, .topnav a:focus-visible { background: var(--mist); }

  .who-btn { max-width: 46vw; }
  .who-drop { right: 0; min-width: 190px; }
}

/* On touch there is no way to "un-hover", so a tap on the account button left
   its dropdown stuck open — sitting on top of the nav panel. Here it opens on
   an explicit tap only. */
@media (hover: none) {
  .who-menu:hover .who-drop, .who-menu:focus-within .who-drop { display: none; }
  .who-menu.is-open .who-drop { display: block; }
}

/* Nothing should ever scroll the page sideways. clip (not hidden) so the
   sticky topbar keeps working — hidden would make body a scroll container. */
html { overflow-x: clip; }
body { overflow-x: clip; max-width: 100%; }

/* ==========================================================================
   Touch devices
   A control revealed on :hover doesn't exist on a phone — there is no hover to
   give. Anything that hides until hover has to be plainly visible here.
   ========================================================================== */
@media (hover: none) {
  .client-manage,
  .grid.view-rows .card-actions,
  .grid.view-rows .card:focus-within .card-actions { opacity: 1; pointer-events: auto; }
  .card .mark, .client-pin { opacity: 1; }
  /* the clean list's floating rail would sit on top of the tags on a narrow
     screen, so it goes back into the row's flow */
  .grid.view-list.view-rows .card-actions {
    position: static; grid-column: 1 / -1; grid-row: 2;
    justify-content: flex-start; padding: 2px 0 0; background: none;
    width: 100%; max-width: none;
  }
  .grid.view-rows .card-body { row-gap: 4px; padding-bottom: 8px; }
  /* hover underlines are meaningless on touch and just look like a bug */
  .grid:not(.view-list) .card-actions .btn:hover { text-decoration: none; }

  /* Text-only actions look right but give a ~19px target. Grow the hit area,
     not the type — the buttons look identical, they're just easier to hit. */
  .grid:not(.view-list) .card-actions .btn,
  .grid:not(.view-list) .card-actions .move-select { padding-top: 9px; padding-bottom: 9px; }
  .grid:not(.view-list) .copy.is-icon { padding: 9px 4px; }
  .grid:not(.view-list) .card-more { padding: 9px 4px; }
  .topnav a { padding: 9px 0; }
  .status-badge, .chip, .vt-btn { min-height: 34px; }
  .pub-url, .crumbs a { padding: 4px 0; display: inline-block; }
}

/* ==========================================================================
   Phones
   ========================================================================== */
@media (max-width: 620px) {
  /* --- page frame: nearly edge to edge, so the content gets the screen --- */
  .page { padding-left: 10px; padding-right: 10px; }
  .topbar { padding-left: 12px; padding-right: 12px; }
  .footer { padding-left: 10px; padding-right: 10px; }
  .grid { gap: 8px; }

  /* A list row's body had no min-width, so it couldn't shrink and pushed the
     action rail 30px past the card's own edge — which is what cropped the
     pin, star and ··· off the right. */
  .grid.view-list .card-body { min-width: 0; }
  .grid.view-list .card-actions { min-width: 0; max-width: none; flex-wrap: wrap; }
  /* In a flex column the title sizes to its text and can run past the card, so
     it needs an explicit ceiling before the ellipsis will kick in. */
  .grid.view-list .card-title,
  .grid.view-list .tag-row { max-width: 100%; min-width: 0; }

  /* --- list headers: title, then tools stacked full width --- */
  .team-head-row { flex-direction: column; align-items: stretch; gap: 12px; }
  .team-head-row .head-actions,
  .team-head-row > .btn { width: 100%; }
  .head-actions { display: flex; flex-wrap: wrap; gap: 8px; }
  .activity-search, .invite-fields input, .invite-fields select { flex: 0 0 auto; width: 100%; }
  .head-actions .btn { flex: 1 1 auto; }

  .list-tools, .list-tools-right { flex-direction: column; align-items: stretch; gap: 8px; }
  /* flex-basis is the VERTICAL axis once these stack — leaving flex:1 1 240px
     on the search made it 240px tall instead of wide. */
  .list-tools #client-search, .list-tools #proj-search,
  .head-tools-right #proj-search, .toolbar-top #q {
    flex: 0 0 auto; max-width: none; width: 100%;
  }
  .list-tools .btn, .list-tools-right .btn { width: 100%; }
  .head-tools-right { flex-direction: column; align-items: stretch; gap: 8px; width: 100%; }
  .head-tools-right .view-toggle { width: 100%; }
  .head-tools-right .view-toggle .vt-btn { flex: 1; }

  /* --- rows: the sub-line is useful context on a phone, not clutter --- */
  .disc-sub { display: block; }
  .client-link { padding: 14px 76px 14px 4px; }
  .client-manage { position: absolute; top: 50%; margin-top: -16px; right: 44px; }

  /* --- activity feed: one line per fact, no right-aligned orphan --- */
  .feed-row { gap: 4px 10px; padding: 11px 2px; }
  .feed-what { flex: 1 1 100%; }
  .feed-org { margin-left: 0; }

  /* --- the file toolbar: search on its own row, controls share the next.
         Four stacked full-width rows pushed the files themselves off-screen. --- */
  .toolbar-top { flex-wrap: wrap; align-items: center; gap: 8px; }
  .toolbar-top > * { flex: 0 0 auto; }
  /* card size is decided by the screen here, so the S/M/L pills earn no room */
  .toolbar-top [data-size-target] { display: none; }
  /* Search takes its own row; sort, layout and Select share the next one.
     The auto margin on Select was pushing it past the edge and wrapping it
     onto a third row of its own. */
  .toolbar-top .view-toggle { width: auto; }
  .toolbar-top .view-toggle .vt-btn { flex: 0 0 auto; padding: 8px 9px; font-size: 12px; }
  .toolbar-top .sel-toggle { margin-left: 0; padding: 8px 12px; font-size: 12.5px; }
  /* Spread the three control groups across the row instead of packing them
     left with dead space on the right, and give the toolbar one even rhythm:
     the same gap between search, controls, chips and the grid below. */
  .toolbar-top { gap: 10px; justify-content: space-between; }
  .toolbar-top #q { flex: 0 0 100%; }
  .toolbar { gap: 10px; padding-top: 12px; padding-bottom: 0; }
  .chips { gap: 6px; }
  .folder .grid { padding-top: 10px; }
  /* Lightbox: at phone width the title, the open link and the close were all
     fighting for one row, so the title collapsed into a narrow wrapping column.
     Title gets its own line with the X parked in the corner, the links sit
     under it, and the box uses the whole screen. */
  .player-box { width: 100%; max-width: none; }
  .player-head { position: relative; flex-wrap: wrap; padding: 0 34px 8px 2px; gap: 0; }
  #player-title {
    flex: 1 1 100%; min-width: 0; font-size: 13px; line-height: 1.3;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .player-tools { flex: 1 1 100%; justify-content: flex-start; gap: 8px; margin-top: 8px; }
  .player-close { position: absolute; top: -6px; right: -6px; font-size: 22px; padding: 8px 10px; }
  .player-dl { font-size: 12.5px; padding: 6px 12px; }

  /* the section break was 40px against a 10px rhythm everywhere else */
  .folder { padding-top: 8px; }
  .folder.project-head-wrap { padding-top: 14px; }
  .proj-filter { margin-bottom: 0; }
  /* Eleven tag chips wrapped onto five rows and pushed the files off-screen.
     One swipeable row instead — the same pattern as the project tabs above. */
  .chips {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    -webkit-overflow-scrolling: touch; padding-bottom: 2px;
  }
  .chips::-webkit-scrollbar { display: none; }
  .chips .chip { flex: 0 0 auto; }

  /* Five controls at a 14px gap need ~169px; a two-up card gives ~124px, so the
     star and the ··· were being clipped straight off the tile. Tighten the gap
     and let the row scroll if a longer label still overruns. */
  .grid:not(.view-list) .card-actions { gap: 4px; overflow-x: auto; scrollbar-width: none; }
  .grid:not(.view-list) .card-actions::-webkit-scrollbar { display: none; }
  .grid:not(.view-list) .card-actions .btn { font-size: 12px; }

  /* --- cards: two per row, and shorter so a screenful shows more --- */
  .grid { gap: 10px; }

  /* --- the batch bar keeps to one scrolling row --- */
  .bulk-bar { width: calc(100vw - 16px); bottom: max(10px, env(safe-area-inset-bottom)); }

  /* --- comfortable tap targets --- */
  .mark { width: 34px; height: 34px; }
  .grid.view-list .mark { width: 28px; height: 28px; }
  .chip, .vt-btn, .proj-tab { min-height: 36px; }
  .who-item { padding: 12px 12px; }
}

/* Two columns is the right density on a normal phone — a single full-width
   card per row means scrolling past one file at a time. */
@media (max-width: 620px) {
  .grid:not(.view-list) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* only the smallest handsets drop to one */
@media (max-width: 359px) {
  .grid:not(.view-list) { grid-template-columns: 1fr; }
  .who-btn { max-width: 40vw; }
}

/* ---------- pin & star, inline with the other actions ----------
   They used to float on the artwork as filled chips. In the action row they are
   just glyphs, matched to Open and the copy icon — one row of controls reading
   Open · copy · pin · star · more, and the 16:9 frame stays uncovered. */
.card-actions .mark {
  width: auto; height: auto; border-radius: 10px;
  background: none; border-color: transparent; box-shadow: none;
  color: var(--slate); opacity: 1; backdrop-filter: none;
}
.card-actions .mark svg { width: 13px; height: 13px; }
.card-actions .mark:hover { background: none; transform: none; color: var(--ink); }
.card-actions .mark.is-on, .card-actions .mark.is-fav { background: none; color: var(--ink); }
.card-actions .mark.is-fav { color: var(--fav); }
.grid:not(.view-list) .card-actions .mark { padding: 2px 0; }
.grid.view-list .card-actions .mark { padding: 4px 2px; }
/* the left rail and the padding it reserved are no longer needed */
.grid.view-list .card:has(.card-marks) { padding-left: 0; }
.grid.view-rows .card, .grid.view-rows .card:has(.card-marks) { padding-left: 10px; }
.grid.view-rows.is-selecting .card { padding-left: 40px; }

/* ---------- broken links: the label, on its own ----------
   The chain-link placeholder and the BROKEN LINK badge said the same thing
   twice. Drop the icon and centre the label in the frame by itself. */
.grid:not(.view-list) .card.is-broken .card-matte .card-art-icon { display: none; }
.grid:not(.view-list) .card.is-broken .card-matte .matte-open { display: none; }
.grid:not(.view-list) .card.is-broken .card-matte { gap: 0; }
.grid:not(.view-list) .card.is-broken .broken-badge {
  position: static; transform: none; margin: 0;
  display: inline-flex; align-items: center; justify-content: center;
  text-align: center; line-height: 1; padding: 7px 12px;
}

/* ---------- coming soon ----------
   A card with no link isn't broken, it just isn't ready. Same treatment as the
   broken label so the grid reads consistently, in the amber "in progress" hue
   rather than the red "something is wrong" one. */
.soon-badge {
  position: absolute; top: 10px; left: 10px; z-index: 2;
  background: var(--review); color: #fff; font-family: var(--display); font-weight: 800;
  letter-spacing: .1em; font-size: 9.5px; padding: 5px 9px; border-radius: 999px;
}
.card.is-pending .card-matte { background: var(--review-bg); }
.grid:not(.view-list) .card.is-pending .card-matte .card-art-icon,
.grid:not(.view-list) .card.is-pending .card-matte .matte-open { display: none; }
.grid:not(.view-list) .card.is-pending .card-matte { gap: 0; }
.grid:not(.view-list) .card.is-pending .soon-badge {
  position: static; transform: none; margin: 0;
  display: inline-flex; align-items: center; justify-content: center;
  text-align: center; line-height: 1; padding: 7px 12px;
}
/* list rows have room for the icon, so there the label shortens instead */
.grid.view-list .soon-badge {
  top: 4px; left: 4px; padding: 2px 6px; font-size: 0; white-space: nowrap; line-height: 1.5;
}
.grid.view-list .soon-badge::after { content: attr(data-short); font-size: 8px; letter-spacing: .08em; }
.grid.view-rows .soon-badge { display: none; }

/* ---------- list rows: the type icon sits by the title ----------
   It was pinned to the bottom-left of the thumbnail here while card view and
   the clean list both put it beside the title. Same place in all three now,
   and it leaves the thumbnail clear. */
.grid.view-list .type-badge { display: none; }
.grid.view-list .card-body {
  grid-template-columns: 16px minmax(120px, 1fr) auto;
  column-gap: 9px;
}
.grid.view-list .card-body > * { grid-column: 2; }
.grid.view-list .row-icon {
  display: block; grid-column: 1; grid-row: 1;
  width: 15px; height: 15px; border-radius: 4px; object-fit: contain;
}
.grid.view-list .card-actions { grid-column: 3; }
@media (max-width: 700px) {
  /* the body goes back to a column here, so the icon rides with the title */
  .grid.view-list .row-icon { grid-column: auto; grid-row: auto; }
}

/* the date reads as metadata, not as another tag */
.card-date { flex: 0 0 auto; white-space: nowrap; opacity: .85; }
.grid.view-list .card-date { margin-left: 2px; }

/* one line of plain english at the top of each batch popover */
.pop-hint {
  display: block; font-size: 11.5px; line-height: 1.45; color: var(--slate);
  margin-bottom: 2px; max-width: 300px;
}
.pop-hint strong { color: var(--ink); font-weight: 700; }

/* A video that carries closed captions says so at the head of its title, beside
   the type icon — a fact about the file, not another button. */
.cc-chip {
  display: inline-block; vertical-align: 1px; margin-right: 6px;
  font-family: var(--display); font-size: 8.5px; font-weight: 800; letter-spacing: .08em;
  line-height: 1; padding: 3px 4px; border-radius: 4px;
  background: var(--mist); color: var(--slate); border: 1px solid var(--line);
}

/* The Pinned section is the viewer's own shortlist, not a folder on the file —
   the accent keeps that distinction visible at a glance. */
.pin-head { color: var(--ink); }
.pin-head svg { color: var(--red); }

/* Small tiles: five controls at a 14px gap measured 168px of content into a
   142px row, so the ··· was pushed past the card edge and clipped away. Tighten
   the rhythm, and drop the two controls that repeat something already on screen
   — the secondary open link (the lightbox offers it) and the CC button (the
   chip beside the title already says captions exist). */
.grid.size-s:not(.view-list) .card-actions { gap: 6px; }
/* "Preview" is the widest primary label and the last 16px that won't fit. */
.lbl-short { display: none; }
.grid.size-s:not(.view-list) .lbl-full { display: none; }
.grid.size-s:not(.view-list) .lbl-short { display: inline; }
.grid.size-s:not(.view-list) .card-actions .open-alt,
.grid.size-s:not(.view-list) .card-actions .cc-btn { display: none; }
/* Whatever still overflows scrolls rather than escaping the card. */
.grid.size-s:not(.view-list) .card-actions { overflow-x: auto; scrollbar-width: none; }
.grid.size-s:not(.view-list) .card-actions::-webkit-scrollbar { display: none; }

/* The file count rides along with the section name without shouting like it. */
.group-note {
  font-family: var(--body); font-weight: 500; font-size: 11.5px;
  letter-spacing: 0; text-transform: none; color: var(--slate); margin-left: 2px;
}

/* Pinned is always open, so it carries no Show/Hide pill and no pointer. */
.pin-head::after { content: none; }
.pin-head { cursor: default; }

/* ---------- batch actions: the dialog ----------
   These panes used to be popovers anchored inside .bulk-bar. That bar scrolls
   sideways, and an overflow-x container clips on BOTH axes — so a panel sitting
   above a 54px bar was clipped out of existence even though it was open and
   correctly positioned. Out here, in the middle of the screen, nothing clips. */
.bulk-modal { position: fixed; inset: 0; z-index: 80; display: grid; place-items: center; padding: 20px; }
.bulk-modal[hidden] { display: none; }
.bulk-modal-scrim { position: absolute; inset: 0; background: rgba(18,18,18,.5); }
.bulk-modal-panel {
  position: relative; width: min(720px, 100%); max-height: min(88vh, 760px);
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  background: var(--bg-elev); border: 1px solid var(--line-strong);
  border-radius: 16px; box-shadow: 0 24px 60px rgba(0,0,0,.3); padding: 18px 20px 20px;
}
.bulk-modal-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.bulk-modal-head h3 { margin: 0; font-size: 17px; }
.bulk-modal-count { font-size: 12px; color: var(--slate); }
.bulk-modal-count strong { color: var(--ink); }
.bulk-modal-x {
  margin-left: auto; border: 0; background: none; font-size: 22px; line-height: 1;
  color: var(--slate); cursor: pointer; padding: 2px 6px; border-radius: 8px;
}
.bulk-modal-x:hover { color: var(--ink); background: var(--mist); }
body.bulk-modal-open { overflow: hidden; }

.bulk-pane { display: flex; flex-direction: column; gap: 10px; }
.bulk-pane[hidden] { display: none; }
.bulk-pane input[type="text"], .bulk-pane input[type="search"],
.bulk-pane select, .bulk-pane textarea {
  width: 100%; font: inherit; font-size: 13.5px; padding: 9px 11px;
  border-radius: 10px; border: 1px solid var(--line); background: var(--bg); color: var(--ink);
}
.bulk-pane textarea { resize: vertical; min-height: 84px; }
.bulk-pane .pop-hint { max-width: none; margin: 0; font-size: 12.5px; }
.bulk-pane-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 4px; }
.bulk-pane-actions .hint { margin: 0; font-size: 11.5px; }
.bulk-pane .proj-pick-list {
  display: block; max-height: 300px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: 10px; padding: 4px;
}

/* Email: the form on the left, what they'll receive on the right. */
.mail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start; }
.mail-form { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.mail-form .stack-label { margin-top: 6px; font-size: 12px; font-weight: 700; }
.mail-people-label { margin: 10px 0 2px; font-size: 11.5px; color: var(--slate); }
.mail-people { display: flex; flex-wrap: wrap; gap: 6px; max-height: 132px; overflow-y: auto; }
.mail-person {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  border: 1px solid var(--line); background: var(--mist); border-radius: 10px;
  padding: 5px 9px; font: inherit; cursor: pointer; text-align: left; color: var(--ink);
}
.mail-person:hover { border-color: var(--ink); }
.mail-person.is-on { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }
.mail-person-name { font-size: 12.5px; font-weight: 600; line-height: 1.2; }
.mail-person-sub { font-size: 10.5px; opacity: .7; line-height: 1.2; }
.mail-preview { min-width: 0; }
.mail-preview-label { margin: 6px 0 4px; font-size: 12px; font-weight: 700; }
.mail-preview-box {
  border: 1px solid var(--line); border-radius: 12px; background: var(--bg);
  padding: 12px; max-height: 320px; overflow-y: auto;
}
.mail-note {
  margin: 0 0 10px; padding-bottom: 10px; border-bottom: 1px solid var(--line);
  font-size: 12.5px; white-space: pre-wrap; overflow-wrap: anywhere;
}
.mail-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.mail-row { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mail-row-title { font-size: 12.5px; font-weight: 600; color: var(--red); overflow-wrap: anywhere; }
.mail-row-kind { font-size: 10.5px; color: var(--slate); }
.mail-row.is-dead .mail-row-title { color: var(--slate); text-decoration: line-through; }
.mail-empty { margin: 0; font-size: 12.5px; color: var(--slate); }

@media (max-width: 640px) {
  .bulk-modal { padding: 10px; }
  .bulk-modal-panel { padding: 14px; max-height: 92vh; }
  .mail-grid { grid-template-columns: 1fr; gap: 12px; }
  .mail-preview-box { max-height: 200px; }
}
/* "Message (optional)" is one line, not a heading with a subtitle under it. */
.mail-form .stack-label { display: block; }
.mail-form .stack-label .muted { font-weight: 500; }
/* A manual download link is a secondary action next to Open — and on a small
   tile it steps aside like the other secondaries, since the lightbox carries it. */
.grid.size-s:not(.view-list) .card-actions .dl-btn { display: none; }

/* The ··· must never be pushed out of the card by the row it sits in. Adding a
   Download button made a Drive PDF seven controls wide and knocked it out at
   full size — the same failure the small tiles had, just needing one more
   button. So it leaves the flow entirely: anchored bottom-right of the card,
   with the actions row scrolling underneath it and reserving its width. */
.grid:not(.view-list) .card-rest { position: relative; }
.grid:not(.view-list) .card-actions {
  overflow-x: auto; scrollbar-width: none;
}
.grid:not(.view-list) .card-actions::-webkit-scrollbar { display: none; }

.grid:not(.view-list) .card.is-picked .admin-card-tools,
.grid:not(.view-list) .card:hover .admin-card-tools { background: var(--bg-elev); }

/* A photo in the lightbox is fitted to the frame, never cropped or blown up —
   an <img> in place of the iframe, sized to contain. */
.player-frame img { width: 100%; height: 100%; object-fit: contain; display: block; }
.player-frame img[hidden] { display: none; }
.player--image .player-frame { background: var(--ink-deep); }

/* Files page: the crumbs and the Add file button share one line, the way the
   project header does. */
.files-topline {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.files-topline .crumbs { margin: 0; }
/* The project picker leads the add row, so it reads as the first decision. */
.af-project {
  font: inherit; font-size: 13.5px; padding: 9px 11px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  max-width: 220px;
}

/* Pin and star are drawn filled when on, outlined when off — but the server
   renders that difference in the SVG itself, so a click that only toggles a
   class left the glyph outlined until the page reloaded. Make the class produce
   the same filled shape, so the state is visible the instant it changes. */
.mark.is-on .pin-glyph path,
.mark.is-fav .fav-glyph path { fill: currentColor; stroke: none; }
.mark:not(.is-on) .pin-glyph path,
.mark:not(.is-fav) .fav-glyph path {
  fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linejoin: round;
}

/* ---------- phones: a quiet tile, and a sheet for everything else ----------
   Six controls squeezed into a 165px column made every one of them a poor tap
   target. On a phone the tile now carries only what you READ (artwork, type
   icon, title) plus the two things you toggle constantly (pin, star). Tapping
   the tile raises a sheet with the full set at a comfortable size.
   These rules are appended so they win over the earlier .card-actions block. */
@media (max-width: 620px) {
  /* the tile: pin and star survive, every other control moves to the sheet */
  .grid:not(.view-list) .card-actions > *:not(.mark):not(.admin-card-tools) { display: none; }
  /* Desktop parks the marks on the right edge; here they are the only things
     left in the row and they are centred, so that auto margin has to go or it
     shoves the pair back over to the right. */
  .grid:not(.view-list) .card-actions .mark.pin { margin-left: 0; }
  /* Centred as a pair under the title — the two controls read as one small
     cluster belonging to the card, rather than drifting into a corner. */
  .grid:not(.view-list) .card-actions {
    gap: 10px; padding: 0; overflow: visible;
    justify-content: center; align-items: center; min-height: 40px;
  }
  .grid:not(.view-list) .card-actions .mark {
    width: 40px; height: 40px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .grid:not(.view-list) .card-actions .mark svg { width: 17px; height: 17px; }

  /* Thumbnail, icon, title, pin, star — nothing else. Tags and the date are in
     the filter chips and the sheet; on a 165px tile they only crowded the name. */
  .grid:not(.view-list) .tag-row { display: none; }
  /* Two lines of title, clipped cleanly — and ALWAYS two lines' worth of box,
     even for a short name. Cards stretch to a shared height, so without a
     reserved title box a one-line card pushed its pin and star 17px higher than
     its neighbour's. Reserving the space is what lines the marks up. */
  /* The desktop sizes pin the body to a fixed height and .card clips, so a body
     that needs more than that gets its bottom sliced off — which on a phone is
     exactly where the pin and star sit. This has to name each size class to win:
     .grid.size-s:not(.view-list) .card-body is 0-4-0 and beats a plain
     .grid:not(.view-list) .card-body (0-3-0) no matter which is written later or
     that this one is inside a media query. Heights stay uniform because the
     reserved two-line title box below fixes the content height, not this. */
  .grid:not(.view-list) .card-body,
  .grid.size-s:not(.view-list) .card-body,
  .grid.size-m:not(.view-list) .card-body,
  .grid.size-l:not(.view-list) .card-body { height: auto; min-height: 0; }
  /* line-height is set here rather than inherited so the reserved box and the
     clamp agree — deriving min-height from an assumed ratio left a third line
     half-visible under the ellipsis. display must be -webkit-box for the clamp
     to engage at all; a later rule was computing it to flow-root. */
  .grid:not(.view-list) .card-body .card-title,
  .grid.size-s:not(.view-list) .card-body .card-title {
    display: -webkit-box; -webkit-box-orient: vertical;
    -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
    line-height: 1.3; min-height: 2.6em;
  }
  .grid:not(.view-list) .card-actions .mark svg { width: 16px; height: 16px; }
  /* the ··· is redundant once the whole tile opens the sheet */
  .grid:not(.view-list) .admin-card-tools { display: none; }
  .grid:not(.view-list) .card { cursor: pointer; }

  /* the sheet itself */
  .card-sheet {
    position: fixed; inset: 0; z-index: 85; display: flex;
    /* The desktop overlay is a column; inheriting that turned "flex-end" into
       "right edge" and left the sheet floating in the middle of the screen. */
    flex-direction: column; justify-content: flex-end; align-items: stretch;
    gap: 0; background: rgba(18,18,18,0); padding: 0; border-radius: 0;
    overflow: visible;
    transition: background .2s ease;
  }
  /* Parked on <body> while open, so the panel can slide from the true screen
     edge instead of being clipped inside the card's overflow. */
  .card-sheet[data-parked] { border-radius: 0; }
  .card-sheet.is-up { background: rgba(18,18,18,.5); }
  .card-sheet[hidden] { display: none; }
  .card-sheet-panel {
    width: 100%; max-height: 82vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
    display: flex; flex-direction: column; gap: 10px;
    background: var(--bg-elev); color: var(--ink);
    border-top-left-radius: 18px; border-top-right-radius: 18px;
    border-top: 1px solid var(--line-strong);
    padding: 8px 16px calc(20px + env(safe-area-inset-bottom));
    box-shadow: 0 -18px 40px rgba(0,0,0,.28);
    /* Slides up from the bottom edge of the SCREEN. A transition (not an
       animation) so the resting state is the stylesheet's, not a keyframe's —
       an animation previously left a residual 14px translate behind. */
    transform: translateY(100%);
    transition: transform .22s cubic-bezier(.22,.61,.36,1);
  }
  .card-sheet.is-up .card-sheet-panel { transform: translateY(0); }
  @media (prefers-reduced-motion: reduce) {
    .card-sheet, .card-sheet-panel { transition: none; }
    .card-sheet-panel { transform: translateY(0); }
  }
  /* Dragging the sheet down closes it. contain stops the scroll chaining to the
     page behind; pan-y still lets a tall sheet scroll its own actions, and the
     drag only takes over once that scroll is already at the top. */
  /* touch-action: none, NOT pan-y. pan-y hands every vertical gesture to the
     browser's scroller, which then fires pointercancel and killed the drag on a
     real phone — the reason swipe-to-close felt unreliable. The panel owns the
     gesture instead. Only a sheet whose content genuinely overflows gets pan-y
     back (JS adds .is-scrollable), and there the drag waits for scrollTop 0. */
  .card-sheet-panel { overscroll-behavior: contain; touch-action: none; }
  .card-sheet-panel.is-scrollable { touch-action: pan-y; }
  /* While a finger is on it the panel follows the finger exactly — the easing
     belongs to the release, not to the drag. */
  .card-sheet-panel.is-dragging { transition: none; }
  /* the grab handle — and now it really does drag */
  .card-sheet-panel::before {
    content: ''; align-self: center; width: 38px; height: 4px; border-radius: 999px;
    background: var(--line-strong); margin: 4px 0 6px; flex: 0 0 auto;
  }
  .card-sheet-head { display: flex; align-items: center; gap: 9px; padding-right: 52px; min-height: 34px; }
  .sheet-icon { width: 18px; height: 18px; border-radius: 5px; object-fit: contain; flex: 0 0 auto; }
  .card-sheet-title {
    font-weight: 700; font-size: 15px; line-height: 1.3; overflow-wrap: anywhere;
  }
  /* the close button belongs to the panel, not the screen — absolute against
     .card-sheet put it in the far corner of the scrim, miles from the sheet */
  .card-sheet-panel { position: relative; }
  .card-sheet .menu-close {
    position: absolute; top: 14px; right: 14px; z-index: 2;
    width: 34px; height: 34px; border-radius: 999px; border: 1px solid var(--line);
    background: var(--bg); color: var(--slate); font-size: 20px; line-height: 1; cursor: pointer;
  }
  /* one full-width row per action, at a real tap size */
  .card-sheet-actions, .card-sheet form, .card-sheet .move-select { display: block; width: 100%; }
  .card-sheet-actions { display: flex; flex-direction: column; gap: 8px; }
  .card-sheet .btn, .card-sheet .move-select {
    width: 100%; justify-content: flex-start; text-align: left;
    padding: 13px 14px; font-size: 15px; border-radius: 12px;
    border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  }
  .card-sheet .btn-primary { background: var(--ink); color: var(--on-ink); border-color: var(--ink); }
  .card-sheet .btn-danger { color: var(--danger); }
  .card-sheet .copy.is-icon svg { margin-right: 10px; flex: 0 0 auto; }
  /* beats .grid:not(.view-list) .copy.is-icon, which squeezes it to an icon */
  .grid:not(.view-list) .card-sheet .copy.is-icon {
    padding: 13px 14px; width: 100%; height: auto; justify-content: flex-start;
  }
  /* the tile hides these labels to save room; in the sheet there is room */
  .grid.size-s:not(.view-list) .card-sheet .lbl-full { display: inline; }
  .grid.size-s:not(.view-list) .card-sheet .lbl-short { display: none; }
  .grid:not(.view-list) .card-sheet .copy.is-icon .copy-word,
  .grid:not(.view-list) .card-sheet .copy-pass.is-icon .copy-word { display: inline; }
  .grid.size-s:not(.view-list) .card-sheet .open-alt,
  .grid.size-s:not(.view-list) .card-sheet .cc-btn,
  .grid.size-s:not(.view-list) .card-sheet .dl-btn { display: block; }
  /* selecting files takes over the tile — the sheet must not be reachable */
  .grid.is-selecting .card .card-sheet { display: none !important; }
}


/* Desktop keeps the ··· overlay exactly as it was: the sheet chrome and the
   duplicated action rows are phone-only. */
@media (min-width: 621px) {
  .admin-card-tools.tools-phone { display: none; }
  /* Centred panel rather than a bottom sheet: on a pointer screen the sheet
     idiom reads as a modal, and the grab handle means nothing to a mouse. */
  .card-sheet { justify-content: center; align-items: center; padding: 24px; }
  .card-sheet-panel {
    max-width: 420px; border-radius: 16px; border: 1px solid var(--line-strong);
    max-height: min(640px, calc(100vh - 80px));
    padding: 14px 16px 16px;
    box-shadow: 0 24px 60px rgba(0,0,0,.35);
    transform: translateY(10px); opacity: 0;
    transition: transform .18s ease, opacity .18s ease;
  }
  .card-sheet.is-up .card-sheet-panel { transform: translateY(0); opacity: 1; }
  .card-sheet-panel::before { display: none; }
}

/* ---------- the add-file project picker ----------
   Borrows the bulk bar's search-a-list popover so the two read as one control
   in both themes, rather than a second invention. */
.af-pick { position: relative; display: inline-flex; flex: 0 0 auto; }
.af-pick-btn {
  font: inherit; font-size: 13.5px; padding: 9px 11px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  max-width: 220px; min-width: 150px; text-align: left; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.af-pick-btn:hover { border-color: var(--ink); }
.af-pick-btn.has-pick { border-color: var(--ink); font-weight: 600; }
.af-pick-btn.needs-pick { border-color: var(--danger); color: var(--danger); }
.af-pick-panel {
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0;
  width: min(340px, calc(100vw - 40px));
  display: flex; flex-direction: column; gap: 8px; padding: 10px;
  border-radius: 12px; background: var(--bg-elev); border: 1px solid var(--line-strong);
  box-shadow: 0 12px 30px rgba(0,0,0,.22);
}
.af-pick-panel[hidden] { display: none; }
/* Two classes deep on purpose: `.addlink-flex input` is 0-1-1 and would
   otherwise win. flex-basis is the VERTICAL axis inside this column panel, so
   inheriting the add-form's `flex: 1 1 160px` made the search box 160px TALL. */
.af-pick-panel .af-pick-find {
  flex: 0 0 auto; width: 100%; height: auto; min-height: 0;
  font: inherit; font-size: 13px; padding: 8px 10px;
  border-radius: 9px; border: 1px solid var(--line); background: var(--bg); color: var(--ink);
}
.af-pick-list {
  flex: 0 1 auto; display: flex; flex-direction: column; max-height: 260px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: 10px; padding: 4px;
}
/* .proj-pick-row was written for a <label>; as a button it needs resetting */
.af-pick-row {
  width: 100%; border: 0; background: none; font: inherit; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  text-align: left; padding: 8px 9px; border-radius: 8px; color: var(--ink);
}
.af-pick-row[hidden] { display: none; }
.af-pick-row:hover, .af-pick-row.is-active { background: var(--mist); }
.af-pick-row[aria-selected="true"] { background: var(--ink); color: var(--on-ink); }
.af-pick-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.af-pick-all {
  border: 0; background: none; font: inherit; font-size: 12px; font-weight: 600;
  color: var(--slate); cursor: pointer; padding: 2px; text-align: left;
}
.af-pick-all:hover { color: var(--ink); }
.af-pick-empty { margin: 0; font-size: 12.5px; color: var(--slate); }
@media (max-width: 620px) {
  .af-pick, .af-pick-btn { width: 100%; max-width: none; }
  .af-pick-panel { width: calc(100vw - 32px); }
}

/* ---------- add-file: the submit reads as the action ----------
   .btn-primary is var(--fog) — a pale grey that is fine on a card full of ghost
   buttons but leaves a FORM with no obvious way to finish it. Here the submit is
   filled, so it is the one thing in the form that carries weight, and it sits
   after every field rather than in the first row above them. */
.addlink-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin-top: 12px;
  padding-top: 12px; border-top: 1px solid var(--line);
}
.addlink-foot .hint { margin: 0; flex: 1 1 260px; }
.addlink .addlink-go {
  flex: 0 0 auto; background: var(--ink); color: var(--on-ink); border-color: var(--ink);
  font-weight: 700; padding: 11px 22px; border-radius: 11px; font-size: 14px;
}
.addlink .addlink-go:hover {
  background: var(--red); border-color: var(--red); color: #fff;
}
.addlink .addlink-go:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
@media (max-width: 620px) {
  /* thumb-reachable and unmissable: the full width of the form */
  .addlink-foot { flex-direction: column-reverse; align-items: stretch; }
  .addlink .addlink-go { width: 100%; padding: 14px 18px; font-size: 15px; }
  .addlink-foot .hint { flex: 0 0 auto; }
}

/* ---------- touch: reachable controls, and no zoom-on-focus ----------
   Two conditions, deliberately: a coarse pointer catches an iPad in landscape at
   1200px, and a narrow width catches a phone browser that reports a fine pointer.
   Either one means fingers.

   Placed last in the file so it beats the container rules that were quietly
   undoing the earlier attempt: .invite-fields input sets `font: inherit`, and the
   font shorthand RESETS font-size — at 0-1-1 it outranked the bare
   `input, select, textarea` anti-zoom rule (0-0-1), so every field on the member
   and account forms stayed at 13px. */
@media (max-width: 1024px), (pointer: coarse) {
  /* Under 16px, iOS Safari zooms the page when a field takes focus and does not
     zoom back out — the single most disruptive thing about these forms on a
     phone. The containers are named as well as the bare elements so the rule
     wins wherever it has to. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
  select, textarea,
  .invite-fields input, .invite-fields select,
  .authcard input,
  .tag-newrow input,
  .list-tools input, .toolbar input[type="search"], .activity-search,
  /* These two are styled through an ID (1-1-0), which no class selector can
     outrank however late it appears — so they have to be named the same way. */
  .list-tools #client-search, .list-tools #proj-search,
  .head-tools-right #proj-search, .toolbar-top #q,
  .addlink input, .addlink select,
  .edit-body input, .edit-body select, .edit-body textarea,
  .bulk-pane input, .bulk-pane select, .bulk-pane textarea,
  .domain-add input, .af-pick-find,
  /* the activity filter styles itself through .w-auto, which beat the plain
     element selectors above */
  select.w-auto, .activity-filter select { font-size: 16px; }

  /* A 13px checkbox is a 13px target. Native controls scale cleanly, and the
     label beside them already carries the click. */
  input[type="checkbox"], input[type="radio"] { width: 20px; height: 20px; flex: 0 0 auto; }

  /* Segmented controls were 29-30px wide — under a fingertip that is a coin toss
     between S and M. */
  .vt-btn, .size-btn { min-width: 40px; }
  /* Badges that are really buttons need a button's height. */
  .status-badge { min-height: 32px; }
}

/* Phone only: a form's actions stack and go full width. On a narrow screen a
   left-aligned 100px "Save changes" is both the smallest target on the page and
   the easiest to miss at the end of a long form. Kept to phones — an iPad has
   the width for the buttons to stay on one row. */
@media (max-width: 620px) {
  .invite-actions { flex-direction: column; align-items: stretch; gap: 8px; }
  .invite-actions .btn { width: 100%; }
}

/* ---------- phone: give the content the top of the screen ----------
   .marquee-art is ordered first on a narrow screen, which is right when it holds
   a real still from the project and wrong when it holds the branded placeholder:
   a member opening the portal on their phone met ~190px of empty folder icon
   before their project's name. With no artwork the tile carries no information,
   so it goes; with artwork nothing changes. */
@media (max-width: 620px) {
  .marquee-noart .marquee-art { display: none; }

  /* The section heading and its controls were a space-between row, so on a phone
     the control groups wrapped into three ragged right-aligned rows beside a
     narrow title — three different widths, none of them lining up. Stacked, the
     title gets its own line and the pills run left to right across the full
     width, which is both tidier and wider targets. */
  .folder-head { flex-direction: column; align-items: stretch; gap: 12px; }
  .folder-head h2 { margin: 0; }
  .folder-head .head-tools { justify-content: flex-start; width: 100%; }
  /* .view-toggle carries margin-left:auto so it hugs the right on a wide toolbar.
     Once the groups are stacked under their own heading that auto margin is what
     stranded the last one alone on its own row — every group packs left here. */
  .folder-head .head-tools .view-toggle { margin-left: 0; }
}

/* NOTE: a `white-space: nowrap` on these facts used to live here, to stop
   "updated Aug 2, 2026" orphaning its year. Dropping the year from the markup
   solved that properly, and the nowrap turned out to be actively harmful: the
   card body is a GRID with a fixed height (70px at size S), so forcing the three
   facts onto three lines grew .card-meta to 52px and the grid starved the title's
   row to 0px — the badge then painted straight over the project name on desktop.
   Left deliberately absent. */

/* ---------- "ALL PROJECTS" header: layout toggle rides the heading ----------
   Desktop keeps exactly what it had — heading left, then sort, size and layout
   packed together at the right — because .head-tools takes the auto margin and
   the layout toggle follows it. */
.folder-head-split { flex-wrap: wrap; }
.folder-head-split .head-tools { margin-left: auto; }

@media (max-width: 620px) {
  /* Overrides the generic phone rule that stacks every .folder-head: here the
     heading shares its line with the layout toggle, and only sort and size drop
     to the row below. Card-or-list is the control you reach for first and it was
     the one marooned on a third row. */
  .folder-head-split { flex-direction: row; align-items: center; gap: 10px 12px; }
  .folder-head-split h2 { flex: 1 1 auto; }
  .folder-head-split .vt-layout { order: 2; margin-left: auto; flex: 0 0 auto; }
  .folder-head-split .head-tools { order: 3; flex: 1 1 100%; margin-left: 0; justify-content: flex-start; }
}

/* ---------- RECENTLY ADDED FILES: a glance, not a gallery ----------
   The strip scrolls sideways, so every tile shares the height of the tallest one.
   A vertical clip was taking a 9:16 matte — 295px of artwork on a 168px-wide tile
   — which made the whole strip 441px on a phone: most of the screen spent on one
   recent file. In a row like this a common height matters more than each file's
   own shape, and the art is object-fit: cover, so a reel still shows its middle
   rather than being squashed. The full 9:16 treatment stays everywhere it is the
   point: the grid tile, and the lightbox. */
.strip-row .card.is-vertical .card-matte { aspect-ratio: 16 / 9; }
/* Tags and the date are one tap away on the file itself; here they were a
   two-line block on every tile, and this row is meant to be skimmed. */
.strip-row .tag-row { display: none; }

/* The strip's controls needed 309px inside a ~150px tile, so the row was cut off
   mid-word — "Copy lin…" — and that was true at every width, not just on a phone:
   the tiles stay tile-sized on a desktop too, and still overflowed by ~140px. A
   tile this size carries what the grid tiles carry: what you do with the file,
   plus the two marks you toggle. Copy, Download and CC live on the file's own
   card, one tap away, and this row exists to be skimmed. */
/* CORRECTION to the block above: keeping Play + pin + star on a strip tile was
   wrong, because .strip-cover (:751-756) is position:absolute inset:0 z-index:5
   over the whole tile and .card-actions has no stacking context — so those three
   controls were UNCLICKABLE at every width. 37px of a ~200px tile spent on
   buttons that do nothing. A covered tile loses the row entirely.
   Scoped with :has(.strip-cover) because _card.php:78 only sets $projLink when
   the file's project resolves; a card without one emits no cover and its buttons
   really do work, so it keeps a (shortened) row. */
.strip-row .card:has(.strip-cover) .card-actions { display: none; }
.strip-row .card-actions > .copy,
.strip-row .card-actions > .copy-pass,
.strip-row .card-actions > .dl-btn,
.strip-row .card-actions > .cc-btn,
.strip-row .card-actions > .open-alt,
.strip-row .card-actions > .admin-card-tools { display: none; }

@media (max-width: 620px) {
  /* Both strips wrap their heading and their nav onto separate rows at slightly
     different widths, so "RECENTLY ADDED FILES" pushed View all / ← / → down onto
     a row of their own while "RECENTLY OPENED" kept them alongside. Same shape as
     the ALL PROJECTS header now: title left, controls right, one line. */
  .strip-head { flex-wrap: nowrap; align-items: center; gap: 10px; }
  .strip-head .strip-title { flex: 1 1 auto; min-width: 0; }
  .strip-head .strip-nav { flex: 0 0 auto; margin-left: auto; }
}

/* ---------- file inputs: the one control still wearing the browser's clothes ----------
   Every field in these forms is a 10px-rounded box, but the "Choose File" button
   inside a file input is a shadow-DOM button the page had never styled — so it
   sat there with square corners and the platform's own border, inside a rounded
   container. Both spellings: ::file-selector-button is the standard, the -webkit-
   one is what older Safari answers to. */
input[type="file"] { color: var(--slate); }
input[type="file"]::file-selector-button {
  font: inherit; font-weight: 700;
  color: var(--ink); background: var(--bg);
  border: 1px solid var(--line); border-radius: 9px;
  padding: 7px 12px; margin: 0 10px 0 0; cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
input[type="file"]::-webkit-file-upload-button {
  font: inherit; font-weight: 700;
  color: var(--ink); background: var(--bg);
  border: 1px solid var(--line); border-radius: 9px;
  padding: 7px 12px; margin: 0 10px 0 0; cursor: pointer;
}
input[type="file"]::file-selector-button:hover { border-color: var(--ink); background: var(--mist); }
input[type="file"]::-webkit-file-upload-button:hover { border-color: var(--ink); background: var(--mist); }

/* A file input is as wide as the filename the browser decides to show, and it
   does not shrink below its content unless told to — so on a phone the "Image"
   row pushed "No file chosen" straight out through the side of the form. The
   label takes its own line and the control takes the width that is left. */
@media (max-width: 620px) {
  .file-pick { flex-direction: column; align-items: stretch; gap: 6px; }
  .file-pick input[type="file"] { flex: 0 1 auto; width: 100%; min-width: 0; }
}

/* The status badge now rides the breadcrumb line rather than trailing the title. */
.crumbs .status-badge { margin-left: 8px; vertical-align: middle; }

@media (max-width: 620px) {
  /* These five were flex-end with margin-left:auto — built for a wide header,
     where they sit tidily at the right. On a phone that same rule wrapped them
     into ragged right-aligned rows of two and three, hugging the edge with a
     gap of dead space beside them. Here they share the row instead: two even
     halves per line, and "+ Add file" — the primary — takes the full last row. */
  .proj-head-actions {
    margin-left: 0; width: 100%; justify-content: flex-start; gap: 8px;
  }
  .proj-head-actions .btn {
    flex: 1 1 calc(50% - 4px); min-width: 0; justify-content: center;
  }
  .proj-head-actions .btn-primary { flex-basis: 100%; }
  /* The member-side header does not use .proj-head-actions at all — its single
     "+ Add a link to file or folder" is a direct child of .folder-head, which is
     align-items:center here, so it would sit centred at its natural width while
     everything above it ran edge to edge. It gets the full row like the rest. */
  .project-head-wrap .folder-head { align-items: stretch; }
  .project-head-wrap .folder-head > .btn { width: 100%; }
}

/* ---------- project cards: a fixed body height must never starve the title ----------
   .card-body is a grid, and the size classes pin it to a fixed height (70px at S).
   A project's meta line is three facts — status, file count, updated — which on a
   ~134px card wrap to three lines and take 52px. The grid then had nothing left
   for row 1 and collapsed the TITLE to 0px, so the status badge painted straight
   over the project's name. Visible on desktop at size S on every card; it never
   showed on a phone because the body is height:auto there.

   The fixed height becomes a floor instead: short cards look exactly as before,
   and a card that needs more room takes it rather than eating its own heading.
   Named per size class to outrank .grid.size-s:not(.view-list) .card-body (0-4-0). */
.grid.grid-projects:not(.view-list) .card-body { height: auto; min-height: 70px; }
.grid.grid-projects.size-s:not(.view-list) .card-body { height: auto; min-height: 70px; }
.grid.grid-projects.size-m:not(.view-list) .card-body { height: auto; min-height: 126px; }
.grid.grid-projects.size-l:not(.view-list) .card-body { height: auto; min-height: 130px; }

/* ---------- client page: project rows that hold their shape ----------
   The summary is a 3-track grid instead of a flex row, so the name always gets
   the leftover width and the status/count pair keeps a fixed column. The name
   truncates rather than wrapping — a wrapped name was what made rows 98px on one
   line and 143px on another down the same list. */
.proj-sum {
  display: grid; align-items: center;
  /* Four tracks, not three. A plain 1fr name column pushed the status badge to the
     far right of a 1240px page — measured 700-830px of nothing between a project's
     name and its own status. The name column is capped instead, so the badges form
     an aligned column a readable distance from the names, and the flexible track
     sits AFTER the meta, leaving the chevron anchored at the row's right edge
     where a "this row opens" affordance belongs.
     A PERCENTAGE, not a rem cap: minmax(0, 34rem) is content-sized up to its
     maximum, and each row is its own grid, so short names produced narrower name
     columns and the badges landed at three different x positions at iPad width.
     A percentage is the same on every row at any viewport. */
  grid-template-columns: minmax(0, 55%) auto minmax(0, 1fr) auto;
  grid-template-areas: "name meta gap chev";
  column-gap: 16px; row-gap: 3px;
}
.proj-sum .proj-name { grid-area: name; }
.proj-sum .disc-meta { grid-area: meta; flex-wrap: nowrap; }
.proj-sum .chev      { grid-area: chev; justify-self: end; }
.proj-name { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* one column edge for every badge, so the counts line up down the list */
.proj-sum .status-badge { min-width: 116px; }
/* Preview and Settings, now in the body where the rest of the project's controls
   already are, separated from the fields below. */
.proj-rowtools {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 2px 0 10px; margin-bottom: 4px; border-bottom: 1px solid var(--line);
}
@media (max-width: 620px) {
  /* A phone has no room for name and meta side by side, so meta drops under the
     name and the chevron spans both — still two tidy left-aligned lines, not a
     squeezed two-column fight. */
  .proj-sum {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "name chev" "meta chev";
    column-gap: 12px; row-gap: 2px;
  }
  .proj-sum .disc-meta { justify-content: flex-start; }
}

@media (max-width: 620px) {
  /* The tag chips wrapped to three ragged rows inside an already-tall form. The
     stylesheet solved this exact problem for .chips 1800 lines earlier — one
     swipeable row. Same treatment, same reasoning. */
  .tag-suggest {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    -webkit-overflow-scrolling: touch; padding-bottom: 2px;
  }
  .tag-suggest::-webkit-scrollbar { display: none; }
  .tag-suggest .chip { flex: 0 0 auto; }

  /* The three primary fields had flex bases of 240/190/160 against a 313-328px
     content box, so nothing ever paired and they stacked as three full-width
     rows with the widest gaps in the form. The link keeps its own row; title and
     folder share one. */
  .addlink-flex { gap: 6px; }
  .addlink-flex input { flex: 1 1 120px; }
  .addlink-flex input[name="url"] { flex: 1 1 100%; }
  .addlink { padding: 12px; }
  .addlink-img { margin-top: 6px; }
  .addlink-more { margin-top: 8px; }
  .addlink-foot { margin-top: 10px; padding-top: 10px; gap: 8px; }
}

/* Section headings inside a .rows list (the members list groups pinned members
   the same way the file grid does). */
.rows-head {
  list-style: none; padding: 14px 2px 6px; border-bottom: 1px solid var(--line);
  font-family: var(--display); font-size: 11px; font-weight: 800;
  letter-spacing: .16em; text-transform: uppercase; color: var(--slate);
}
.rows-head:first-child { padding-top: 4px; }
.rows-head[hidden] { display: none; }

@media (max-width: 620px) {
}

/* Public links: condensed by default, expandable. The hiding is CSS-only so the
   footprint is capped even before JS runs; the button only flips a class. */
.pub-links.has-more .pub-list > li:nth-child(n+4) { display: none; }
.pub-links.has-more.is-open .pub-list > li:nth-child(n+4) { display: flex; }
.pub-more { margin-top: 8px; }

/* Paste / drop an image straight into a file field. */
.paste-hint {
  display: block; width: 100%; margin-top: 4px;
  font-size: 11.5px; font-weight: 400; color: var(--slate);
}
.file-pick.drop-image, .stack-label.drop-image {
  outline: 2px dashed var(--red); outline-offset: 3px; border-radius: 10px;
}
/* a brief, quiet confirmation that the paste landed */
.got-image .paste-hint { color: var(--ok, var(--ink)); font-weight: 700; }
.got-image .paste-hint::after { content: ' — got it'; }

/* The file grid renders even when empty (so the admin wiring that looks for it
   still runs), so its empty message has to span the tracks rather than sit in
   the first column. */
.grid > .grid-empty { grid-column: 1 / -1; margin: 6px 0 0; }

/* ---------- the two page-creating forms wear their card's colour ----------
   A watch page card glows purple and a review card is amber, so the button that
   creates one should already look like the thing it will make. */
.create-watch {
  background: var(--watch-btn); border-color: var(--watch-btn-line); color: var(--watch-btn-ink);
}
.create-watch:hover, .create-watch:focus-visible {
  background: var(--watch-btn-hover); border-color: var(--watch-btn-line); color: var(--watch-btn-ink);
}
.create-review {
  background: var(--review-btn); border-color: var(--review-btn-line); color: var(--review-btn-ink);
}
.create-review:hover, .create-review:focus-visible {
  background: var(--review-btn-hover); border-color: var(--review-btn-line); color: var(--review-btn-ink);
}
/* the close beside them is quiet by comparison — it is the way out, not the action */
.addlink-foot .addlink-close { margin-left: 8px; }

/* ---------- the random-address toggle ----------
   It is a sentence, not a field, so it takes its own line instead of competing
   with the inputs beside it. Its checkbox also has to opt out of the
   `.addlink-flex input { flex: 1 1 160px }` rule above — that selector matches
   descendants, so it was stretching a checkbox into a 160px-wide flex item and
   wrapping the label text around it. */
.addlink-flex .slug-random {
  flex: 1 1 100%; margin-top: 2px; align-items: center; font-size: 12.5px; font-weight: 500;
}
.addlink-flex .slug-random input[type="checkbox"] {
  flex: 0 0 auto; width: 16px; height: 16px; margin: 0;
}
.slug-random .muted { font-weight: 400; }

/* ---------- stored project passwords ----------
   The row IS the copy button, so it gets the affordance of one: a whole-row hit
   target, a lock that picks up the accent on hover, and a "Copy" that turns into
   confirmation in place rather than a toast that steals focus. */
.secrets-wrap { margin-top: 22px; }
/* Title and its one control share a line, the way the other section heads do. */
.secrets-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; padding-bottom: 8px; border-bottom: 1px solid var(--line);
}
.secrets-title { display: flex; align-items: center; gap: 7px; margin: 0; font-size: 12px;
                 font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: var(--slate); }
.secrets { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 6px; }
.secret-row { position: relative; display: grid; gap: 2px; }

.secret-copy {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 11px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: inherit; font: inherit; cursor: pointer;
}
.secret-copy:hover { border-color: var(--watch-btn-line); background: var(--mist); }
.secret-copy:focus-visible { outline: 2px solid var(--watch-btn-ink); outline-offset: 2px; }
.secret-lock { display: inline-flex; color: var(--slate); flex: 0 0 auto; }
.secret-copy:hover .secret-lock { color: var(--watch-btn-ink); }
.secret-text { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; min-width: 0; flex: 1 1 auto; }
.secret-label { font-size: 13.5px; font-weight: 600; }
.secret-user { font-size: 12px; color: var(--slate); word-break: break-all; }
.secret-hint { font-size: 11.5px; font-weight: 700; color: var(--slate); flex: 0 0 auto;
               letter-spacing: .04em; text-transform: uppercase; }
.secret-copy.is-copied { border-color: var(--ok, #2E7D52); }
.secret-copy.is-copied .secret-hint, .secret-copy.is-copied .secret-lock { color: var(--ok, #2E7D52); }
.secret-copy.is-failed { border-color: var(--broken); }
.secret-copy.is-failed .secret-hint, .secret-copy.is-failed .secret-lock { color: var(--broken); }

/* Edit and Remove sit beside the row and stay visible — a hover-only control is
   unreachable on touch, and these are the only way to correct a stored password. */
/* A grid, not wrapping flex: the copy button and its two controls belong on one
   line and the note belongs under both. Flex-wrap could only ever put the note
   beside them or push the controls onto a line of their own. */
.secret-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; column-gap: 8px;
}
/* Rows are pinned, not auto-placed: the note sits before the tools in the
   markup (it belongs to the entry, not to the controls), and grid does not
   backfill, so the tools would otherwise land on a third row of their own. */
.secret-row > .secret-copy { grid-column: 1; grid-row: 1; min-width: 0; }
.secret-tools { grid-column: 2; grid-row: 1; display: inline-flex; align-items: center; gap: 2px; }
.secret-edit, .secret-remove {
  border: 0; background: none; color: var(--slate); font: inherit; font-size: 11.5px;
  font-weight: 700; letter-spacing: .03em; padding: 5px 7px; border-radius: 7px; cursor: pointer;
}
.secret-edit:hover { color: var(--watch-btn-ink); background: var(--mist); }
.secret-remove:hover { color: var(--broken); background: var(--broken-bg); }
.secret-edit:focus-visible, .secret-remove:focus-visible { outline: 2px solid var(--watch-btn-ink); outline-offset: 1px; }
.secret-notes { grid-column: 1 / -1; grid-row: 2; margin: 3px 0 0 35px; font-size: 12px; color: var(--slate); }
@media (max-width: 560px) { .secret-hint { display: none; } }

/* ---------- card action row: even spacing, and the ··· on the same centre line ----------
   Two separate faults produced the same "it looks off" impression on desktop.

   1. The controls were spread by `margin-left: auto` on .copy, which is not
      spacing — it is a shove. Everything before it sat left, everything after it
      sat right, and the 6px gap applied only within each clump. The row now
      distributes its children evenly instead.
   2. .admin-card-tools is positioned (it carries the fade that keeps a too-narrow
      row from looking guillotined) and was pinned to `bottom: 0`, so the ···
      hung ~6px below the centre line every other icon shares. Spanning top to
      bottom and centring inside puts it on that line. */
.grid:not(.view-list) .card-actions {
  /* No reserved lane any more: the ··· is gone from tiles at every width — the
     whole tile opens the sheet, so a second doorway was clutter. The remaining
     controls share the full row at even intervals. */
  justify-content: space-between;
}
.grid:not(.view-list) .admin-card-tools { display: none; }
.grid:not(.view-list) .card-actions .copy,
.grid:not(.view-list) .card-actions .mark.pin { margin-left: 0; }


/* The password lock beside Copy link — same shape of control, its own accent.
   On a tile the label is hidden, so the colour change IS the confirmation; the
   word only appears in the mobile sheet where there is room for it. */
.copy-pass.is-icon { color: var(--slate); }
.copy-pass.is-icon:hover { color: var(--review-btn-ink); }
/* Specificity, not order: `.grid:not(.view-list) .card-actions .btn` is 0-3-0 and
   sets colour on every control in the row, so a bare `.copy-pass.is-copied`
   (0-2-0) would lose and the confirmation would never show. These match it. */
.grid:not(.view-list) .card-actions .copy-pass.is-copied,
.copy-pass.is-copied { color: var(--ok, #2E7D52); }
.grid:not(.view-list) .card-actions .copy-pass.is-failed,
.copy-pass.is-failed { color: var(--broken); }

/* the closed-captions icon reads like its neighbours */
.cc-btn.is-icon { color: var(--slate); }
.cc-btn.is-icon:hover { color: var(--ink); }
.card-sheet .cc-btn.is-icon svg { margin-right: 10px; flex: 0 0 auto; }

/* ---------- editor project access ---------- */
.editor-access { border-top: 1px solid var(--line); padding-top: 12px; margin-top: 12px; }
.access-modes { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 10px; }
.access-modes .check { font-size: 13.5px; }
.access-projects {
  max-height: 260px; overflow-y: auto; border: 1px solid var(--line);
  border-radius: 10px; padding: 8px 10px; display: flex; flex-direction: column; gap: 4px;
}
.access-org {
  margin: 8px 0 2px; font-size: 11px; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--slate);
}
.access-org:first-child { margin-top: 0; }
.access-proj { font-size: 13.5px; font-weight: 500; }
.access-proj input { flex: 0 0 auto; width: 15px; height: 15px; margin: 0; }
/* Editor reads as its own thing, not a lesser manager. */
.role-pill.role-editor { background: var(--watch-btn); color: var(--watch-btn-ink); border-color: var(--watch-btn-line); }
