/* ============================================================
   index.html's stylesheet. d.html mirrors parts of it in its own inline
   CSS: viewer / now playing / card fixes usually belong in both.
   ------------------------------------------------------------
   MAP — section headers below, in file order; grep the heading text
   (e.g. "---------- rail ----------"). Keep in sync when a section is
   added, renamed or reordered.
   ------------------------------------------------------------
   brand logo · shell layout · topbar · now playing (topbar) · new file menu · theme toggle · debug menu · account switcher
   · recovery key · rail · main · intro card · drop zone toggle · toast · upload progress · card thumbnails
   · card download · multi-select · collab · collab lock · collab tools menu · cut/copy clipboard · drag-to-move · swipe indicator badges
   · custom scroll indicator · back-to-top button · go-to-location button
   · friendly modal · navigation progress bar + lock · upbin viewer · mobile
   ============================================================ */
:root{
  --bg:#FAFAFA;
  --surface:#FFFFFF;
  --ink:#12151C;
  --ink-soft:#5B6270;
  --ink-faint:#8A90A0;
  --line:#E5E8EF;
  --line-strong:#D6DAE3;
  --accent:#2F5FE0;
  --accent-ink:#1E45B8;
  --accent-soft:#EAF0FE;
  --public:#178A56;
  --public-soft:#E6F6EE;
  /* collab folders (grep "collab"): sky blue, clearly apart from --accent (= "selected") */
  --collab:#0784B5;
  --collab-soft:#E2F3FA;
  --radius-sm:8px;
  --radius-md:12px;
  --radius-lg:16px;
  --font-brand:'Rubik', system-ui, sans-serif;
  --font-body:'Inter', system-ui, sans-serif;

  color-scheme:light;              /* native scrollbars / form controls follow the theme */
  --sun-display:none;              /* theme button: moon in light, sun in dark */
  --moon-display:block;
}
:root:not([data-theme="light"]){
  @media (prefers-color-scheme: dark){
    --bg:#0F1115;
    --surface:#171A21;
    --ink:#F1F2F5;
    --ink-soft:#A7ACBA;
    --ink-faint:#6C7180;
    --line:#262A34;
    --line-strong:#333846;
    --accent:#5A85F5;
    --accent-ink:#8CA9F8;
    --accent-soft:#1A2440;
    --public:#3FC585;
    --public-soft:#123423;
    --collab:#3DB8EA;
    --collab-soft:#0E2B38;
    color-scheme:dark;
    --sun-display:block;
    --moon-display:none;
  }
}
:root[data-theme="dark"]{
  --bg:#0F1115;
  --surface:#171A21;
  --ink:#F1F2F5;
  --ink-soft:#A7ACBA;
  --ink-faint:#6C7180;
  --line:#262A34;
  --line-strong:#333846;
  --accent:#5A85F5;
  --accent-ink:#8CA9F8;
  --accent-soft:#1A2440;
  --public:#3FC585;
  --public-soft:#123423;
  --collab:#3DB8EA;
  --collab-soft:#0E2B38;
  color-scheme:dark;
  --sun-display:block;
  --moon-display:none;
}

*{box-sizing:border-box;}
html,body{margin:0;padding:0;}
html{ overflow-x:hidden; }
body{
  background:var(--bg);
  color:var(--ink);
  font-family:var(--font-body);
  -webkit-font-smoothing:antialiased;
  min-height:100vh;
  overflow-x:clip; /* not "hidden": hidden makes body a scroll container and breaks position:sticky */
  width:100%;
}
a{color:inherit;text-decoration:none;}
button{font-family:inherit;}

/* Set until the web fonts are ready (index-fonts.js), so nothing paints
   with fallback metrics and then resizes. */
html.fonts-loading .shell{ visibility:hidden; }

/* ---------- brand logo ----------
   Inline SVG mark ("up" + "bin") + live Rubik wordmark. Tile =
   var(--accent) (follows the theme), glyph white. Same drawing with a
   fixed light accent in public/favicon.svg and d.html/admin.html: keep in sync. */
.brand{
  display:flex; align-items:center; gap:8px; flex:none;
  font-family:var(--font-brand); font-weight:800; font-size:21px;
  letter-spacing:-.3px; line-height:1; color:var(--ink);
}
.brand-mark{ width:28px; height:28px; flex:none; display:block; }

/* ---------- shell layout ---------- */
.shell{
  display:grid;
  grid-template-columns:232px 1fr;
  grid-template-rows:60px 1fr;
  grid-template-areas:
    "topbar topbar"
    "rail   main";
  min-height:100vh;
}
.menu-btn{ display:none; }

/* ---------- topbar ---------- */
.topbar{
  /* A sticky grid item only travels inside its own grid area (60px row),
     so it spans every row (still 60px tall, start-aligned) to stick. */
  grid-column:1 / -1;
  grid-row:1 / -1;
  align-self:start;
  min-width:0;  /* grid item in a 1fr (= minmax(auto,1fr)) track: without this its min-content (e.g. the now-playing sticker) widened the page on phones */
  height:60px;
  position:sticky;
  top:0;
  z-index:20;
  display:flex;
  align-items:center;
  gap:20px;
  padding:0 20px;
  border-bottom:1px solid var(--line);
  background:var(--surface);
}

.search{
  flex:1;
  max-width:440px;
  display:flex;
  align-items:center;
  gap:8px;
  background:var(--bg);
  border:1px solid var(--line);
  border-radius:var(--radius-sm);
  padding:8px 12px;
  color:var(--ink-faint);
}
.search input{
  border:none;background:none;outline:none;color:var(--ink);
  font-size:14px;font-family:var(--font-body);width:100%;
}
.search input::placeholder{ color:var(--ink-faint); }
.search svg{ flex:none; }
/* "/" shortcut hint: hidden while focused / non-empty (:placeholder-shown)
   and on touch devices. */
.search-kbd{
  flex:none; margin-left:auto;
  min-width:20px; height:20px; padding:0 6px; box-sizing:border-box;
  display:inline-flex; align-items:center; justify-content:center;
  font-family:var(--font-body); font-size:12px; line-height:1; font-weight:500;
  color:var(--ink-faint); background:var(--surface);
  border:1px solid var(--line-strong); border-radius:5px;
  pointer-events:none; user-select:none;
}
.search:focus-within .search-kbd,
.search input:not(:placeholder-shown) ~ .search-kbd{ display:none; }
@media (hover:none){ .search-kbd{ display:none; } }
/* Clear (X): takes the "/" badge's spot; exactly one shows (same trick). */
.search-clear{
  display:none; flex:none; margin-left:auto;
  width:20px; height:20px; padding:0; box-sizing:border-box;
  align-items:center; justify-content:center;
  color:var(--ink-faint); background:transparent;
  border:none; border-radius:50%; cursor:pointer;
}
.search-clear:hover{ background:var(--line); color:var(--ink); }
.search-clear:focus-visible{ outline:2px solid var(--accent); outline-offset:1px; }
.search input:not(:placeholder-shown) ~ .search-clear{ display:inline-flex; }

/* Active-search glow: `.searching` from syncSearchGlow(), off state.search
   (trimmed), not :placeholder-shown, so spaces alone don't glow. Independent
   of focus: the grid is still search results. */
.search{ transition:border-color .2s, box-shadow .2s, background-color .2s; }
.search.searching{
  background:color-mix(in srgb, var(--accent) 6%, var(--surface));
  border-color:var(--accent);
  color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent),
             0 0 14px color-mix(in srgb, var(--accent) 35%, transparent);
  animation:search-glow 2.4s ease-in-out infinite;
}
@keyframes search-glow{
  0%,100%{ box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent),
                      0 0 10px color-mix(in srgb, var(--accent) 25%, transparent); }
  50%{ box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent),
                  0 0 18px color-mix(in srgb, var(--accent) 50%, transparent); }
}
@media (prefers-reduced-motion: reduce){ .search.searching{ animation:none; } }

/* Phone stand-in for .search (display:none at <=760px): a faint magnifier
   opening the friendly modal in search mode (openSearchModal). Gets
   `.searching` like .search. */
.search-peek{
  display:none; align-items:center; justify-content:center;
  /* 36px = same tap target as .theme-btn. */
  width:36px; height:36px; padding:0; flex:none;
  border:none; border-radius:50%; background:transparent;
  color:var(--ink-faint); opacity:.75; cursor:pointer;
  transition:color .2s, opacity .2s, background-color .2s, box-shadow .2s;
}
.search-peek:hover{ opacity:1; }
.search-peek:focus-visible{ outline:2px solid var(--accent); outline-offset:1px; }
.search-peek.searching{
  opacity:1; color:var(--accent);
  background:color-mix(in srgb, var(--accent) 10%, var(--surface));
  box-shadow:0 0 0 2px color-mix(in srgb, var(--accent) 45%, transparent),
             0 0 12px color-mix(in srgb, var(--accent) 35%, transparent);
  animation:search-glow 2.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce){ .search-peek.searching{ animation:none; } }

.topbar-actions{ display:flex; align-items:center; gap:12px; margin-left:auto; }

/* ---------- now playing (topbar) ----------
   The background track (script: grep "now playing") as a topbar sticker,
   findable after leaving its folder. Only while a track is loaded.
   Desktop: right of .search, fixed width. Phone: after the logo mark,
   shrinking (the wordmark steps aside: .topbar.np-on).
   TWO halves, hairline between:
     - .np-open: mini waveform (dances while sounding, faint when paused).
       Click = viewer on the track; hover turns the bars into an expand icon.
     - .np-toggle: the name (card-name marquee keyframes, measured by the
       script). Click = pause / resume. The play/pause glyph's slot is
       always reserved (marquee measure stays fixed); shown on hover / focus,
       always on touch.
   Each half lights on its own hover. Progress line along the bottom (--audio-p). */
.now-playing{
  flex:none; width:230px; height:36px; position:relative; overflow:hidden;
  display:flex; align-items:stretch;
  border:1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
  border-radius:var(--radius-sm);
  background:color-mix(in srgb, var(--accent) 7%, var(--surface));
  color:var(--ink); font-family:var(--font-body); font-size:13px; font-weight:600;
  transition:border-color .15s ease;
  animation:np-in .2s ease;
}
.now-playing[hidden]{ display:none; }
@keyframes np-in{ from{ opacity:0; transform:translateY(-4px); } to{ opacity:1; transform:none; } }
.now-playing button{
  display:flex; align-items:center; margin:0; border:none; background:transparent;
  color:inherit; font:inherit; text-align:left; cursor:pointer;
  -webkit-tap-highlight-color:transparent; transition:background-color .15s ease;
}
.now-playing button:focus-visible{ outline:2px solid var(--accent); outline-offset:-2px; border-radius:var(--radius-sm); }
.now-playing button:active{ background:color-mix(in srgb, var(--accent) 18%, transparent); }
@media (hover:hover){
  .now-playing:hover{ border-color:var(--accent); }
  .now-playing button:hover{ background:color-mix(in srgb, var(--accent) 12%, transparent); }
}
.now-playing .np-open{
  flex:none; width:42px; justify-content:center; padding:0; position:relative;
  border-right:1px solid color-mix(in srgb, var(--accent) 25%, var(--line));
}
.np-wave{ display:flex; align-items:center; gap:2px; width:22px; height:18px; color:var(--accent); transition:color .2s ease, opacity .15s ease, transform .15s ease; }
.np-wave i{ flex:1; height:var(--h); border-radius:99px; background:currentColor; transform:scaleY(.5); transform-origin:center; }
.now-playing.playing .np-wave i{ animation:card-wave .8s ease-in-out infinite alternate; animation-delay:var(--d); }
.now-playing:not(.playing) .np-wave{ color:var(--ink-faint); }
.np-open-ico{ position:absolute; left:50%; top:50%; margin:-8px 0 0 -8px; color:var(--accent); opacity:0; transform:scale(.6); transition:opacity .15s ease, transform .15s ease; pointer-events:none; }
.np-open:focus-visible .np-wave{ opacity:0; transform:scale(.6); }
.np-open:focus-visible .np-open-ico{ opacity:1; transform:none; }
@media (hover:hover){
  .np-open:hover .np-wave{ opacity:0; transform:scale(.6); }
  .np-open:hover .np-open-ico{ opacity:1; transform:none; }
}
.np-toggle{ flex:1; min-width:0; gap:6px; padding:0 12px 0 8px; }
.np-state{ flex:none; width:12px; height:12px; display:flex; align-items:center; justify-content:center; color:var(--accent); opacity:0; transition:opacity .15s ease; }
.np-state svg{ display:block; }
.np-state .ico-pause{ display:none; }
.now-playing.playing .np-state .ico-play{ display:none; }
.now-playing.playing .np-state .ico-pause{ display:block; }
.np-toggle:focus-visible .np-state{ opacity:1; }
@media (hover:hover){ .np-toggle:hover .np-state{ opacity:1; } }
@media (hover:none){ .np-state{ opacity:.9; } }
.np-name{ flex:1; min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
@media (prefers-reduced-motion: no-preference){
  .np-name.marquee{
    text-overflow:clip;
    -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 8px, #000 calc(100% - 8px), transparent 100%);
            mask-image:linear-gradient(90deg, transparent 0, #000 8px, #000 calc(100% - 8px), transparent 100%);
  }
  .np-name.marquee .np-name-text{ display:inline-block; padding:0 8px; margin-left:-8px; animation:card-name-marquee var(--marquee-dur, 4s) linear infinite alternate; will-change:transform; }
}
@media (prefers-reduced-motion: reduce){ .now-playing.playing .np-wave i{ animation:none; transform:scaleY(.8); } }
.np-progress{ position:absolute; left:0; right:0; bottom:0; height:2px; pointer-events:none; }
.np-progress::before{ content:""; position:absolute; inset:0; transform-origin:left; transform:scaleX(var(--audio-p, 0)); background:var(--accent); }
@media (max-width: 1040px){ .now-playing{ width:190px; } }
/* .np-compact (fitNowPlayingName: name would get < NP_MIN_NAME_PX): no
   name, waveform + play/pause only. Two-class selectors on purpose: they
   beat the phone block below. */
.now-playing.np-compact{ flex:none; width:auto; }
.np-compact .np-name{ display:none; }
.now-playing.np-compact .np-toggle{ flex:none; padding:0 11px; }
.np-compact .np-state{ width:13px; height:13px; opacity:1; }
.np-compact .np-state svg{ width:13px; height:13px; }
/* Phone: takes what's left of the bar (down to just the waveform half). */
@media (max-width: 760px){
  .now-playing{ flex:0 1 200px; width:auto; min-width:44px; height:34px; font-size:12.5px; }
  .now-playing .np-open{ width:38px; }
  .np-toggle{ padding:0 9px 0 7px; gap:5px; }
  .topbar.np-on .brand > span{ display:none; }
}

.btn{
  display:inline-flex; align-items:center; gap:7px;
  border-radius:var(--radius-sm);
  font-family:var(--font-body);
  font-weight:600;
  font-size:14px;
  padding:9px 15px;
  border:1px solid transparent;
  cursor:pointer;
}
.btn-primary{ background:var(--accent); color:#fff; }
.btn-primary:hover{ background:var(--accent-ink); }
.btn-ghost{ background:transparent; border-color:var(--line-strong); color:var(--ink); }
.btn-ghost:hover{ background:var(--bg); }

/* ---------- new file menu ----------
   "+ File/Folder" (#newBtn) + #newPop (Folder / Txt); script: index-nav.js
   "new file menu". Same look as the debug menu's pop. Phone (<=360px):
   "+" and .nf-icon only. */
.new-wrap{ position:relative; flex:none; }
#newBtn .nf-icon{
  display:none; align-items:center; color:currentColor;
}
#newBtn .nf-icon svg{ display:block; }
#newBtn[aria-expanded="true"]{ background:var(--bg); }
.new-pop{
  position:absolute; top:calc(100% + 6px); right:0;
  min-width:160px;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--radius-md);
  box-shadow:0 12px 32px rgba(18,21,28,.14);
  padding:6px;
  display:none;
  z-index:30;
}
.new-pop.open{ display:block; }
.new-item{
  display:flex; align-items:center; gap:10px; width:100%;
  padding:9px 10px; border:none; border-radius:var(--radius-sm);
  background:none; color:var(--ink); font:inherit; font-size:14px; font-weight:500;
  text-align:left; cursor:pointer; white-space:nowrap;
}
.new-item:hover, .new-item:focus-visible{ background:var(--bg); outline:none; }
.new-item:disabled{ opacity:.5; cursor:default; }
.new-item svg{ flex:none; color:var(--ink-soft); }
/* Split row: .new-item asks the name; .new-quick (right half) creates the
   default name and opens the viewer. At rest only hinted (faint dashed
   outline + icon); hover / focus = toned-down drop-target look; hovering
   the left half wakes it to the .drop-near step so it's found. */
.new-row{ display:flex; align-items:stretch; gap:4px; }
.new-row .new-item{ flex:1 1 50%; min-width:0; }
.new-quick{
  flex:0 0 calc(50% - 2px);
  display:flex; align-items:center; justify-content:center;
  margin:3px 3px 3px 0; padding:0; border:none; border-radius:6px; cursor:pointer;
  color:color-mix(in srgb, var(--accent) 45%, transparent);
  background:color-mix(in srgb, var(--accent) 5%, transparent);
  outline:1.5px dashed color-mix(in srgb, var(--accent) 28%, transparent); outline-offset:-1.5px;
  transition:background .15s ease, color .15s ease, outline-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.new-quick svg{ display:block; }
.new-row:hover .new-quick{
  color:var(--accent);
  background:color-mix(in srgb, var(--accent) 12%, transparent);
  outline-color:color-mix(in srgb, var(--accent) 60%, transparent);
}
.new-row .new-quick:hover, .new-row .new-quick:focus-visible{ /* beats .new-row:hover .new-quick */
  /* Softer than the crumb's .drop-target (mid-drag needs loud): see-through accent, solid outline. */
  color:var(--accent);
  background:color-mix(in srgb, var(--accent) 20%, transparent);
  outline:1.5px solid color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow:none;
  transform:scale(1.03);
}
.new-quick:disabled{ opacity:.5; cursor:default; transform:none; }
@media (prefers-reduced-motion: reduce){ .new-quick:hover, .new-quick:focus-visible{ transform:none; } }

/* ---------- theme toggle ---------- */
.theme-btn{
  display:flex; align-items:center; justify-content:center;
  width:36px; height:36px; flex:none;
  border:1px solid var(--line-strong); border-radius:var(--radius-sm);
  background:var(--surface); color:var(--ink-soft); cursor:pointer;
}
.theme-btn:hover{ background:var(--bg); color:var(--ink); }
.theme-btn:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.theme-btn .i-sun{ display:var(--sun-display); }
.theme-btn .i-moon{ display:var(--moon-display); }

/* ---------- debug menu ----------
   Wrench + dropdown left of #themeBtn, only with ?debug=true (grep
   `debugWrap`). New dev actions = more .debug-item buttons. */
.debug-wrap{ position:relative; flex:none; }
.debug-wrap[hidden]{ display:none; }
.debug-btn.open{ background:var(--bg); color:var(--ink); }
.debug-pop{
  position:absolute; top:calc(100% + 8px); right:0;
  min-width:180px;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--radius-md);
  box-shadow:0 12px 32px rgba(18,21,28,.14);
  padding:6px;
  display:none;
  z-index:30;
}
.debug-pop.open{ display:block; }
.debug-pop .label{ font-size:11px; color:var(--ink-faint); padding:6px 10px 4px; letter-spacing:.02em; }
.debug-item{
  display:flex; align-items:center; gap:8px; width:100%;
  padding:8px 10px; border:none; border-radius:var(--radius-sm);
  background:none; color:var(--ink); font-size:13.5px; font-weight:500;
  text-align:left; cursor:pointer; white-space:nowrap;
}
.debug-item:hover, .debug-item:focus-visible{ background:var(--bg); outline:none; }
.debug-item svg{ flex:none; color:var(--ink-faint); }

/* ---------- account switcher ---------- */
.account-trigger{
  display:flex; align-items:center; gap:8px;
  border:1px solid var(--line-strong);
  background:var(--surface);
  border-radius:999px;
  padding:4px 10px 4px 4px;
  cursor:pointer;
  position:relative;
}
.avatar{
  width:28px;height:28px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  font-family:var(--font-brand);font-weight:600;font-size:12px;color:#fff;
  flex:none;
}
.account-trigger .chev{ color:var(--ink-faint); }

.account-pop{
  position:absolute; top:52px; right:20px;
  width:260px;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--radius-md);
  box-shadow:0 12px 32px rgba(18,21,28,.14);
  padding:8px;
  display:none;
  z-index:30;
}
.account-pop.open{ display:block; }
.account-pop .label{
  font-size:11px; color:var(--ink-faint); padding:8px 10px 4px;
  letter-spacing:.02em;
}
.account-row{
  display:flex; align-items:center; gap:10px;
  padding:8px 10px; border-radius:var(--radius-sm);
  cursor:pointer;
}
.account-row:hover{ background:var(--bg); }
.account-row.active{ background:var(--accent-soft); }
.account-row .who{ display:flex; flex-direction:column; line-height:1.25; min-width:0; }
.account-row .who strong{ font-size:13.5px; font-weight:600; }
.account-row .who span{ font-size:12px; color:var(--ink-faint); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.account-row .tick{ margin-left:auto; color:var(--accent); flex:none; }
.account-pop hr{ border:none; border-top:1px solid var(--line); margin:6px 2px; }
.account-row.add{ color:var(--ink-soft); font-size:13.5px; font-weight:500; }
.account-row.add .avatar{ background:var(--bg); border:1px dashed var(--line-strong); color:var(--ink-faint); }
/* account remove / delete (index-core.js, same heading). The × on a
   "Switch to" row: faint, shows on row hover / focus, always on touch. */
.acct-remove{
  margin-left:auto; flex:none;
  width:26px; height:26px; padding:0;
  display:flex; align-items:center; justify-content:center;
  border:0; border-radius:var(--radius-sm);
  background:none; color:var(--ink-faint); cursor:pointer;
  opacity:0; transition:opacity .12s ease, background .12s ease, color .12s ease;
}
.account-row:hover .acct-remove, .acct-remove:focus-visible{ opacity:1; }
.acct-remove:hover{ background:var(--surface); color:var(--ink); }
.acct-remove:focus-visible{ outline:2px solid var(--accent); outline-offset:1px; }
@media (pointer: coarse){ .acct-remove{ opacity:.75; } }
/* Bottom section: two quiet rows, icon in the avatar column, so it reads
   as part of the list. Delete is the only red thing in the menu. */
.acct-manage{ display:flex; flex-direction:column; }
.acct-manage-row{
  display:flex; align-items:center; gap:10px;
  width:100%; padding:7px 10px; border:0; border-radius:var(--radius-sm);
  background:none; cursor:pointer; text-align:left;
  font-family:var(--font-body); font-size:13px; font-weight:500; color:var(--ink-soft);
}
.acct-manage-row svg{ width:28px; flex:none; color:var(--ink-faint); }
.acct-manage-row:hover{ background:var(--bg); color:var(--ink); }
.acct-manage-row:focus-visible{ outline:2px solid var(--accent); outline-offset:-2px; }
.acct-manage-row.danger, .acct-manage-row.danger svg{ color:#B3261E; }
.acct-manage-row.danger:hover{ background:#FDEDEC; }
:root[data-theme="dark"] .acct-manage-row.danger, :root[data-theme="dark"] .acct-manage-row.danger svg{ color:#F2A7A2; }
:root[data-theme="dark"] .acct-manage-row.danger:hover{ background:#3A1A18; }
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .acct-manage-row.danger, :root:not([data-theme="light"]) .acct-manage-row.danger svg{ color:#F2A7A2; }
  :root:not([data-theme="light"]) .acct-manage-row.danger:hover{ background:#3A1A18; }
}

/* ---------- recovery key (copy / restore) ---------- */
.restore-form{ padding:2px 10px 4px; }
.restore-form label{ display:block; font-size:11px; color:var(--ink-faint); margin:0 0 6px; line-height:1.4; }
.restore-form input{
  width:100%; padding:8px 10px; border:1px solid var(--line); border-radius:var(--radius-sm);
  background:var(--bg); color:var(--ink); font-family:inherit; font-size:12.5px; margin-bottom:8px;
}
.restore-form input:focus{ outline:2px solid var(--accent); outline-offset:1px; }
.restore-form .row{ display:flex; gap:8px; }
.restore-form .row .btn{ flex:1; padding:7px 10px; font-size:12.5px; justify-content:center; }
.restore-form .err{ font-size:11.5px; color:#B3261E; margin:-2px 0 8px; }
:root[data-theme="dark"] .restore-form .err{ color:#F2A7A2; }
@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]) .restore-form .err{ color:#F2A7A2; } }

/* ---------- rail ---------- */
.rail{
  grid-area:rail;
  border-right:1px solid var(--line);
  background:var(--surface);
  padding:18px 12px;
  display:flex;
  flex-direction:column;

  /* under the topbar, one viewport tall: storage / account block always on screen */
  position:sticky;
  top:60px;
  align-self:start;
  height:calc(100vh - 60px);

  /* own scroll when taller than the window; scrollbar invisible until hover / focus */
  overflow-y:auto;
  overscroll-behavior:contain;
  scrollbar-width:thin;
  scrollbar-color:transparent transparent;
}
.rail:hover,
.rail:focus-within{ scrollbar-color:var(--line-strong) transparent; }
.nav{ display:flex; flex-direction:column; gap:2px; }
.nav a{
  display:flex; align-items:center; gap:10px;
  padding:9px 12px;
  border-radius:var(--radius-sm);
  font-size:14px; font-weight:500;
  color:var(--ink-soft);
}
.nav a svg{ flex:none; opacity:.85; }
.nav a:hover{ background:var(--bg); color:var(--ink); }
.nav a.active{ background:var(--accent-soft); color:var(--accent-ink); font-weight:600; }
.nav a.active svg{ opacity:1; }
.nav .count{
  margin-left:auto; font-size:12px; color:var(--ink-faint); font-weight:500;
}
.nav a.active .count{ color:var(--accent-ink); }

.rail-divider{ height:1px; background:var(--line); margin:16px 4px; }

.storage{ padding:2px 12px; }
.storage .row{ display:flex; justify-content:space-between; font-size:12.5px; color:var(--ink-soft); margin-bottom:8px; }
.storage .row .tier{ color:var(--ink-faint); }
.storage .track{ height:6px; border-radius:99px; background:var(--line); overflow:hidden; }
.storage .fill{ height:100%; width:38%; background:var(--accent); border-radius:99px; }
.storage .plan{ margin-top:10px; font-size:12.5px; color:var(--ink-faint); }
.storage .plan strong{ white-space:nowrap; }
.storage .plan a{ color:var(--accent); font-weight:600; }

.rail-foot{ margin-top:auto; padding:0 12px 4px; }
/* rail tip: faint one-liner over #railAcct about hold-to-select and
   drag-to-move (no script). .tip-fine = mouse, .tip-touch = touch wording,
   picked by pointer type (like isCoarsePointer). Verbs a shade darker. */
.rail-tip{
  display:flex; gap:7px; align-items:flex-start;
  margin:0 4px 10px; font-size:11.5px; line-height:1.45;
  color:var(--ink-faint);
}
.rail-tip svg{ flex:none; margin-top:1px; opacity:.8; }
.rail-tip b{ font-weight:600; color:var(--ink-soft); }
.rail-tip .tip-touch{ display:none; }
@media (pointer: coarse){
  .rail-tip .tip-fine{ display:none; }
  .rail-tip .tip-touch{ display:inline; }
}
.rail-account{
  display:flex; align-items:center; gap:9px;
  padding:9px; border-radius:var(--radius-sm);
  border:1px solid var(--line);
  cursor:pointer;
}
.rail-account:hover{ background:var(--bg); }
.rail-account .who{ min-width:0; flex:1; }
.rail-account .who strong{ display:flex; align-items:center; gap:5px; font-size:13px; font-weight:600; min-width:0; }
/* rail account: name ellipsizes (up to ACCOUNT_LABEL_MAX = 32 chars); the
   pencil after it = "click to rename": on hover / focus, always on touch. */
.rail-account .rail-name{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rail-account .rail-edit{ flex:none; color:var(--ink-faint); opacity:0; transition:opacity .12s ease; }
.rail-account:hover .rail-edit,
.rail-account:focus-visible .rail-edit{ opacity:1; }
.rail-account:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
@media (pointer: coarse){ .rail-account .rail-edit{ opacity:.8; } }
/* Wraps instead of ellipsizing: "this browse…" lost its whole point. */
.rail-account .who span{ display:block; font-size:11.5px; line-height:1.35; color:var(--ink-faint); text-wrap:balance; }
.stack{ display:flex; margin-left:auto; }
.stack .avatar{ width:20px; height:20px; font-size:9px; margin-left:-6px; border:2px solid var(--surface); }

/* ---------- main ---------- */
/* flex column: the footer (margin-top:auto) sits at the viewport bottom
   with little content, below it otherwise; the rail's sticky math is untouched. */
.main{ grid-area:main; padding:22px 28px 40px; min-width:0; display:flex; flex-direction:column; }

.crumbs{ display:flex; align-items:center; gap:6px; font-size:13.5px; color:var(--ink-faint); margin-bottom:16px; }
.crumbs a:hover{ color:var(--ink); }
.crumbs .current{ color:var(--ink); font-weight:600; }
/* Crumb share next to the current crumb (script: grep "crumb share"); card
   footer look, .shared badge when a parent shares it. min-height = the
   toggle's 24px so entering a folder doesn't shift the grid. Wraps on
   narrow screens. */
.crumbs{ min-height:24px; flex-wrap:wrap; row-gap:4px; }
.crumb-share{ display:inline-flex; align-items:center; gap:2px; margin-left:4px; }
.crumb-share button.vis-toggle{ border:0; background:none; font-family:inherit; }
.crumb-share .vis-toggle:not(.shared):hover{ background:var(--accent-soft); }
.crumb-share .vis-toggle.busy{ opacity:.6; pointer-events:none; }

.dropzone{
  border:1.5px dashed var(--line-strong);
  border-radius:var(--radius-lg);
  padding:26px 20px;
  display:flex; align-items:center; gap:16px;
  background:var(--surface);
  margin-bottom:22px;
  cursor:pointer;
  transition:background .15s ease, border-color .15s ease;
}
.dropzone:hover{
  background:color-mix(in srgb, var(--accent) 6%, var(--surface));
  border-color:color-mix(in srgb, var(--accent) 35%, var(--line-strong));
}
.dropzone .dz-icon{
  width:44px;height:44px;border-radius:var(--radius-md);
  background:var(--accent-soft); color:var(--accent-ink);
  display:flex;align-items:center;justify-content:center;flex:none;
}
.dropzone .dz-text strong{ display:block; font-size:14.5px; font-weight:600; margin-bottom:2px; }
.dropzone .dz-text span{ font-size:13px; color:var(--ink-faint); }
.dropzone .btn{ margin-left:auto; flex:none; }
/* Browse + Upload folder: one auto margin on the wrapper (per-button
   margins would split the space). Shown only with .dz-folder-capable (JS,
   FOLDER_UPLOAD_SUPPORTED); Android/iOS get .dz-no-folder (pickers can't
   choose a folder). */
.dropzone .dz-actions{ margin-left:auto; display:none; gap:8px; flex:none; }
.dropzone.dz-folder-capable .dz-actions{ display:flex; }
.dropzone .dz-actions .btn{ margin-left:0; }
/* Belt and braces: never on Android/iOS. */
.dropzone.dz-no-folder .dz-actions{ display:none !important; }

/* dragging a file over the window = hover look */
.dropzone.dz-active{
  background:color-mix(in srgb, var(--accent) 6%, var(--surface));
  border-color:color-mix(in srgb, var(--accent) 35%, var(--line-strong));
}

/* ---------- intro card ----------
   #introCard under the drop zone (script: index-core.js, grep "intro card").
   display only when not [hidden] (a class rule would beat [hidden]). Quiet
   on purpose: solid border (the dashed drop zone is the thing to act on). */
.intro-card:not([hidden]){ display:flex; }
.intro-card{
  align-items:flex-start; gap:12px;
  padding:14px 16px;
  border:1px solid var(--line);
  border-radius:var(--radius-lg);
  background:var(--surface);
}
html.dz-hidden:not(.dz-peek) .intro-card, html.collab-locked-view .intro-card{ margin-top:16px; }
.intro-card .intro-icon{
  width:34px; height:34px; flex:none; border-radius:var(--radius-sm);
  display:flex; align-items:center; justify-content:center;
  background:var(--accent-soft); color:var(--accent-ink);
}
.intro-card .intro-body{ min-width:0; flex:1; }
.intro-card strong{ display:block; font-size:14px; font-weight:600; margin:1px 0 3px; }
.intro-card p{ margin:0; font-size:13px; line-height:1.5; color:var(--ink-soft); }
.intro-card p b{ font-weight:600; color:var(--ink); }
.intro-card .intro-actions{ display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; }
.intro-card .intro-actions .btn{ padding:6px 12px; font-size:13px; }

/* ---------- drop zone toggle ----------
   Script: index-upload.js, grep "drop zone toggle" (pref dropzoneHidden).
   html.dz-hidden folds #dzCollapse to 0 with the grid-rows trick (1fr <->
   0fr animates; height:auto can't); the zone fades + lifts. Transitions
   only under .dz-anim (click / peek): boot paint and server corrections
   snap. html.dz-peek = files dragged over the page while hidden: open for
   the drag. #dzToggle at the right end of .crumbs-row, never wraps under. */
.crumbs-row{
  display:flex; align-items:flex-start; gap:12px; margin-bottom:16px;
  transition:margin-bottom .32s cubic-bezier(.2,.8,.2,1);
}
/* Folded: .section-head's 26px top margin is gap enough. */
html.dz-hidden:not(.dz-peek) .crumbs-row{ margin-bottom:0; }
.crumbs-row .crumbs{ flex:1 1 auto; min-width:0; margin-bottom:0; }
.dz-toggle{
  flex:none; display:inline-flex; align-items:center; gap:5px;
  height:32px; margin:-4px 0; padding:0 10px;
  border:1px solid transparent; border-radius:99px;
  background:transparent; color:var(--ink-faint);
  font:inherit; font-size:12.5px; font-weight:600; line-height:1; cursor:pointer;
  transition:color .2s, background-color .2s, border-color .2s;
}
.dz-toggle:hover{ color:var(--ink); background:var(--surface); border-color:var(--line-strong); }
.dz-toggle:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.dz-toggle .dz-toggle-label{ display:none; }
.dz-toggle .dz-chev{ transition:transform .32s cubic-bezier(.2,.8,.2,1); }
/* Hidden: the toggle is the only way to upload, so it says so. */
html.dz-hidden .dz-toggle{
  color:var(--accent-ink); background:var(--accent-soft);
  border-color:color-mix(in srgb, var(--accent) 22%, transparent);
}
html.dz-hidden .dz-toggle:hover{ border-color:color-mix(in srgb, var(--accent) 45%, transparent); }
html.dz-hidden .dz-toggle .dz-toggle-label{ display:inline; }
html.dz-hidden .dz-toggle .dz-chev{ transform:rotate(180deg); }
/* One ring right after hiding: "it went here". */
.dz-toggle.dz-pulse{ animation:dz-toggle-pulse .9s ease-out 1; }
@keyframes dz-toggle-pulse{
  0%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  100%{ box-shadow:0 0 0 10px color-mix(in srgb, var(--accent) 0%, transparent); }
}

.dz-collapse{ display:grid; grid-template-rows:1fr; }
.dz-collapse-inner{ min-height:0; }
/* Clip only while folded or moving, so an open zone's focus ring isn't cut. */
html.dz-hidden .dz-collapse-inner, .dz-collapse.dz-anim .dz-collapse-inner{ overflow:hidden; }
html.dz-hidden .dz-collapse{ grid-template-rows:0fr; }
html.dz-hidden .dz-collapse .dropzone{ opacity:0; transform:translateY(-8px) scale(.985); }
html.dz-hidden.dz-peek .dz-collapse{ grid-template-rows:1fr; }
html.dz-hidden.dz-peek .dz-collapse .dropzone{ opacity:1; transform:none; }
.dz-collapse.dz-anim{ transition:grid-template-rows .32s cubic-bezier(.2,.8,.2,1); }
.dz-collapse.dz-anim .dropzone{
  transition:background .15s ease, border-color .15s ease,
             opacity .24s ease, transform .32s cubic-bezier(.2,.8,.2,1);
}
@media (prefers-reduced-motion: reduce){
  .crumbs-row, .dz-toggle .dz-chev{ transition:none; }
  .dz-toggle.dz-pulse{ animation:none; }
}

/* ---------- toast (showDzMsg) ----------
   Fixed overlay (never moves the page), under the sticky topbar (60px),
   right-aligned on desktop, full width on phones. Not at the bottom: the
   select bar and FABs live there. Stack, newest at the bottom. The
   shrinking .toast-bar is the timer: removal on its animationend, so
   hover / focus pause it by pausing the animation. */
.toast-host{
  position:fixed; top:70px; right:28px; z-index:35;
  width:max-content; max-width:min(400px, calc(100vw - 56px));
  display:flex; flex-direction:column; align-items:stretch; gap:8px;
  pointer-events:none; /* only the toasts themselves take clicks */
}
.toast{
  position:relative; pointer-events:auto;
  display:flex; align-items:flex-start; gap:6px;
  padding:8px 6px 11px 12px; overflow:hidden;
  border-radius:var(--radius-sm); border:1px solid transparent;
  font-size:12.5px; font-weight:600; line-height:1.4;
  box-shadow:0 6px 24px rgba(18,21,28,.16);
  opacity:0; transform:translateY(-6px);
  transition:opacity .18s ease, transform .18s ease;
}
.toast.show{ opacity:1; transform:none; }
.toast.success{ background:var(--public-soft); color:var(--public); border-color:color-mix(in srgb, var(--public) 25%, transparent); }
.toast.error{ background:#FDEDEC; color:#B3261E; border-color:rgba(179,38,30,.25); }
:root[data-theme="dark"] .toast.error{ background:#3A1A18; color:#F2A7A2; border-color:rgba(242,167,162,.25); }
@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]) .toast.error{ background:#3A1A18; color:#F2A7A2; border-color:rgba(242,167,162,.25); } }
/* wraps long filenames / recovery keys */
.toast-text{ flex:1; min-width:0; padding-top:4px; overflow-wrap:anywhere; }
.toast-close{
  flex:none; width:26px; height:26px; padding:0;
  display:flex; align-items:center; justify-content:center;
  border:0; border-radius:6px; background:transparent; color:inherit;
  opacity:.7; cursor:pointer;
}
.toast-close:hover, .toast-close:focus-visible{ opacity:1; background:color-mix(in srgb, currentColor 14%, transparent); }
.toast-bar{
  position:absolute; left:0; right:0; bottom:0; height:3px;
  background:currentColor; opacity:.4;
  transform-origin:left center; transform:scaleX(0);
}
.toast.show .toast-bar{ animation:toast-timer var(--toast-ms, 6000ms) linear forwards; }
@media (hover:hover){ .toast:hover .toast-bar{ animation-play-state:paused; } }
.toast:focus-within .toast-bar{ animation-play-state:paused; }
@keyframes toast-timer{ from{ transform:scaleX(1); } to{ transform:scaleX(0); } }
/* Action button (opts.action) and sticky toasts (no timer: opts.sticky). */
.toast-action{
  flex:none; align-self:center;
  font:inherit; font-size:12.5px; font-weight:700;
  padding:4px 10px; border-radius:99px; cursor:pointer;
  border:1px solid currentColor; background:transparent; color:inherit;
}
.toast-action[hidden]{ display:none; }
.toast-action:hover, .toast-action:focus-visible{ background:color-mix(in srgb, currentColor 14%, transparent); outline:none; }
.toast.sticky .toast-bar{ display:none; }
@media (prefers-reduced-motion: reduce){ .toast{ transition:none; transform:none; } }

/* ---------- upload progress (shown in a card's foot while uploading) ---------- */
.card.uploading .card-name{ overflow:visible; white-space:normal; }
.up-progress{ display:flex; flex-direction:column; gap:6px; width:100%; }
.up-track{ height:5px; border-radius:99px; background:var(--line); overflow:hidden; }
.up-fill{ height:100%; width:0%; background:var(--accent); border-radius:99px; transition:width .15s linear; }
.up-row{ display:flex; justify-content:space-between; font-size:11.5px; color:var(--ink-faint); }
.card.upload-error .up-fill{ background:#D64545; }
.card.upload-error .up-row{ color:#D64545; }
.up-retry{ font-size:11.5px; font-weight:600; color:var(--accent); cursor:pointer; background:none; border:none; padding:0; font-family:inherit; }

/* center, not baseline: the first baseline in .section-head-actions
   changes with the visible buttons, so "Files" jumped ~3px. */
.section-head{ display:flex; align-items:center; gap:10px; margin:26px 0 12px; }
.section-head h2{ font-family:var(--font-brand); font-size:15px; font-weight:600; margin:0; }
.section-head .muted{ font-size:12.5px; color:var(--ink-faint); white-space:nowrap; }
/* (.sort-word: "by name" on phones.) Action buttons pinned to the row's end as one group. */
.section-head-actions{ margin-left:auto; display:flex; align-items:center; gap:8px; }

.grid{
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(190px,1fr));
  gap:12px;
}
.card{
  position:relative;                                /* anchors .select-check */
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--radius-md);
  padding:14px;
  display:flex; flex-direction:column; gap:10px;
  min-width:0;
  cursor:pointer;
  transition:background .15s ease, border-color .15s ease;
  touch-action:none;                                /* JS owns touch gestures on cards, so drag-to-move isn't hijacked by native scrolling on Android (see drag-to-move in index-gestures.js) */
}
/* Faint accent tint (color-mix: right in both themes). */
.card:hover{
  background:color-mix(in srgb, var(--accent) 6%, var(--surface));
  border-color:color-mix(in srgb, var(--accent) 35%, var(--line-strong));
}
.card-top{ display:flex; align-items:flex-start; gap:10px; }
.file-icon{
  width:36px;height:36px;border-radius:9px;flex:none;
  display:flex;align-items:center;justify-content:center;
  font-family:var(--font-brand); font-weight:700; font-size:11px; color:#fff;
}
.card-name{ font-size:13.5px; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* Long-name marquee: ellipsized at rest; on mouse hover the script (grep
   startNameMarquee) sets --marquee-shift / --marquee-dur + .marquee and the
   text bounces to its end and back (alternate). Overflowing names also get
   a title tooltip. .card-name-text is inline at rest (ellipsis applies),
   inline-block only while animating (transform). Reduced motion: tooltip only. */
@media (prefers-reduced-motion: no-preference){
  .card-name.marquee{ text-overflow:clip; }
  .card-name.marquee .card-name-text{ display:inline-block; animation:card-name-marquee var(--marquee-dur, 4s) linear infinite alternate; will-change:transform; }
}
@keyframes card-name-marquee{
  0%, 15%{ transform:translateX(0); }                              /* hold at the start (first hover delay) */
  85%, 100%{ transform:translateX(var(--marquee-shift, 0px)); }   /* hold at the end before bouncing back */
}
.card-meta{ font-size:12px; color:var(--ink-faint); }
/* Search results only: where the item lives ("in Projects / 2026"). */
.card-loc{ font-size:11.5px; color:var(--ink-faint); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* Search results only: text around a content-search hit (index-notes.js), two lines max. */
.card-snip{
  font-size:12px; line-height:1.45; color:var(--ink-soft, var(--ink-faint));
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2;
  overflow:hidden; overflow-wrap:anywhere; margin-top:-2px;
}
.card-snip mark{
  background:color-mix(in srgb, var(--accent) 22%, transparent); color:inherit;
  border-radius:3px; padding:0 1px; font-weight:600;
}
.card-foot{ display:flex; align-items:center; justify-content:space-between; margin-top:auto; padding-top:8px; border-top:1px solid var(--line); }

/* ---------- card thumbnails ----------
   isPreviewable() cards (script: grep "card thumbnails"; images AND
   playable videos) get a .card-thumb layer, display:none until the picture
   really loaded (.has-thumb); slow / failed / refused = normal card.
   <img> = upload-time thumbnail if any (makeImageThumb / makeVideoThumb),
   else the original (images only); videos add a .card-play badge (no
   thumbnail = plain card).
   .has-thumb: the image fills the card (object-fit:cover). .card-top and
   .card-foot float over the edges, shown only on mouse hover, keyboard
   focus, or while selected (touch users can check picks); at rest it's
   just the picture. Frosted theme-surface panels with a hard edge keep
   contrast over any image. */
/* .thumb-loading from attachThumb until shown (.has-thumb) or given up
   (endThumbLoading): a small spinning ring top-right (top-left is
   .select-check's). Reduced motion: still. */
.card.thumb-loading::after{
  content:""; position:absolute; top:12px; right:12px; z-index:2;
  width:12px; height:12px; border-radius:50%;
  border:2px solid var(--line-strong); border-top-color:var(--accent);
  animation:thumb-spin .8s linear infinite;
  pointer-events:none;
}
@keyframes thumb-spin{ to{ transform:rotate(360deg); } }
@media (prefers-reduced-motion: reduce){ .card.thumb-loading::after{ animation:none; } }
.card-thumb{
  display:none;
  position:absolute; inset:0; z-index:0;
  border-radius:calc(var(--radius-md) - 1px);
  overflow:hidden; pointer-events:none;
  background:var(--bg);
}
/* Zoom a little past "cover": subjects are usually central. */
.card-thumb img{ display:block; width:100%; height:100%; object-fit:cover; transform:scale(1.12); transform-origin:center; }
.card-play{
  position:absolute; left:50%; top:50%; width:44px; height:44px; margin:-22px 0 0 -22px;
  border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:rgba(10,12,16,.5); color:#fff;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.3), 0 3px 12px rgba(0,0,0,.3);
  -webkit-backdrop-filter:blur(6px) saturate(140%); backdrop-filter:blur(6px) saturate(140%);
}
.card-play svg{ display:block; } /* no nudge: the glyph is centroid-centred (.uv-play) */
/* card play (script: grep "card play"): video -> viewer playing; audio ->
   background playback (grep "card audio"). .card-thumb is
   pointer-events:none, so the button opts back in. */
button.card-play{ pointer-events:auto; cursor:pointer; border:none; padding:0; z-index:1; transition:background .15s ease, transform .15s ease; -webkit-tap-highlight-color:transparent; }
@media (hover:hover){ button.card-play:hover{ background:rgba(10,12,16,.72); transform:scale(1.08); } }
button.card-play:active{ transform:scale(.94); }
button.card-play:focus-visible{ outline:2px solid #fff; outline-offset:2px; }
.card-play .ico-pause{ display:none; }
.card.audio-playing .card-play .ico-play{ display:none; }
.card.audio-playing .card-play .ico-pause{ display:block; }
/* Audio card (data-audio): .card-thumb is drawn art, shown at once
   (has-thumb): waveform (bars from the file id), note badge top-right,
   play button centred, name along the bottom (a song needs its name).
   .audio-current = the background player's track (accent ring + progress
   line, --audio-p 0..1), .audio-playing = sounding (bars dance, pause
   icon). Hue = the MP3/WAV/FLAC badge colour. */
.card-audio-art{
  --aud:#B8560B;
  position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center;
  background:linear-gradient(140deg, color-mix(in srgb, var(--aud) 22%, var(--surface)), color-mix(in srgb, var(--aud) 7%, var(--bg)));
  color:color-mix(in srgb, var(--aud) 70%, var(--ink));
}
.card-wave{ display:flex; align-items:center; justify-content:center; gap:3px; height:64px; width:100%; padding:0 18px; opacity:.4; transition:opacity .2s ease; }
.card-wave i{ flex:1; max-width:4px; height:var(--h); border-radius:99px; background:currentColor; transform-origin:center; }
.card-audio-note{
  position:absolute; top:10px; right:10px; width:30px; height:30px; border-radius:9px;
  display:flex; align-items:center; justify-content:center;
  background:color-mix(in srgb, var(--aud) 85%, #000); color:#fff;
  box-shadow:0 2px 8px rgba(0,0,0,.18);
}
.card-audio-name{
  position:absolute; left:12px; right:12px; bottom:12px;
  font-size:12.5px; font-weight:600; line-height:1.3; color:var(--ink); text-align:left;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; word-break:break-word;
}
.card-audio-progress{ position:absolute; left:0; right:0; bottom:0; height:3px; background:transparent; }
.card-audio-progress::before{ content:""; position:absolute; inset:0; transform-origin:left; transform:scaleX(var(--audio-p, 0)); background:var(--accent); }
.card.audio-current{ border-color:var(--accent); box-shadow:0 0 0 1px var(--accent); }
.card.audio-current .card-wave{ opacity:.65; }
.card.audio-playing .card-wave i{ animation:card-wave .9s ease-in-out infinite alternate; animation-delay:var(--d); }
@keyframes card-wave{ from{ transform:scaleY(.3); } to{ transform:scaleY(1); } }
@media (prefers-reduced-motion: reduce){ .card.audio-playing .card-wave i{ animation:none; } }
/* The drag ghost (.drag-ghost-inner, a card clone) keeps .has-thumb and
   shares these rules (its top/foot never get :hover). Border stronger than
   --line, or near-white images on light (dark on dark) have no edge. */
:is(.card, .drag-ghost-inner).has-thumb{
  padding:0; min-height:180px; gap:0;
  border-color:color-mix(in srgb, var(--ink) 22%, transparent);
}
:is(.card, .drag-ghost-inner).has-thumb .card-thumb{ display:block; }
.drag-ghost-inner.has-thumb{ position:relative; }
:is(.card, .drag-ghost-inner).has-thumb .card-top,
:is(.card, .drag-ghost-inner).has-thumb .card-foot{
  position:relative; z-index:1;
  background:color-mix(in srgb, var(--surface) 90%, transparent);
  -webkit-backdrop-filter:blur(10px) saturate(1.2);
  backdrop-filter:blur(10px) saturate(1.2);
}
:is(.card, .drag-ghost-inner).has-thumb .card-top{
  position:absolute; top:0; left:0; right:0;
  padding:10px 12px;
  border-bottom:1px solid var(--line-strong);
  border-radius:calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px) 0 0;
  opacity:0; transform:translateY(-4px);
  transition:opacity .15s ease, transform .15s ease;
}
:is(.card, .drag-ghost-inner).has-thumb .card-foot{
  position:absolute; left:0; right:0; bottom:0; margin:0;
  opacity:0; transform:translateY(4px);
  transition:opacity .15s ease, transform .15s ease;
  padding:6px 10px 6px 8px;
  border-top:1px solid var(--line-strong);
  border-radius:0 0 calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px);
}
@media (hover:hover){
  .card.has-thumb:hover .card-top,
  .card.has-thumb:hover .card-foot{ opacity:1; transform:none; }
}
.card.has-thumb:focus-within .card-top,
.card.has-thumb:focus-within .card-foot,
.card.has-thumb.selected .card-top,
.card.has-thumb.selected .card-foot{ opacity:1; transform:none; }
/* Hidden footer controls mustn't catch clicks meant for the image. */
.card.has-thumb .card-foot{ pointer-events:none; }
@media (hover:hover){ .card.has-thumb:hover .card-foot{ pointer-events:auto; } }
.card.has-thumb:focus-within .card-foot,
.card.has-thumb.selected .card-foot{ pointer-events:auto; }
/* Plain-card selected/drop tints are backgrounds the image covers: overlay them. */
.card.has-thumb.selected .card-thumb::after,
.card.has-thumb.drop-target .card-thumb::after{
  content:""; position:absolute; inset:0;
  background:color-mix(in srgb, var(--accent) 22%, transparent);
}

.vis-toggle{
  display:flex; align-items:center; gap:6px;
  font-size:12px; font-weight:600; color:var(--ink-faint);
  cursor:pointer; user-select:none;
  padding:3px 4px 3px 3px; border-radius:99px;
  /* = .card-link-btn's 24px, so the footer doesn't grow 2px when the link button appears. */
  box-sizing:border-box; min-height:24px;
}
.switch{ width:28px; height:16px; border-radius:99px; background:var(--line-strong); position:relative; transition:background .15s; flex:none; }
.switch::after{ content:""; position:absolute; top:2px; left:2px; width:12px; height:12px; border-radius:50%; background:#fff; transition:left .15s; }
.vis-toggle.public{ color:var(--public); }
.vis-toggle.public .switch{ background:var(--public); }
.vis-toggle.public .switch::after{ left:14px; }
.vis-toggle.shared{ cursor:default; } /* inherited from a parent folder: nothing to toggle */

/* Second .card-foot child, only when the item has a working public link
   (own or inherited). Copies the short link (grep `card-link-btn`). */
.card-link-btn{
  display:inline-flex; align-items:center; justify-content:center;
  width:24px; height:24px; padding:0; flex:none;
  border-radius:99px; border:none; background:transparent;
  color:var(--ink-faint); cursor:pointer;
  transition:background .15s ease, color .15s ease;
}
.card-link-btn:hover{ background:var(--accent-soft); color:var(--accent); }

/* ---------- card download (script: grep "card download", index-cards.js +
   index-download.js) ----------
   Takes .card-link-btn's slot (same look) while NOT shared: file =
   download, folder = in-browser zip. Zipping shows a progress ring
   (--zip-p 0..1; spins while .pending = listing); click cancels; .failed
   flashes red. Same ring as d.html's. Also in the crumb share slot. */
.card-dl-btn{
  position:relative;
  display:inline-flex; align-items:center; justify-content:center;
  width:24px; height:24px; padding:0; flex:none;
  border-radius:99px; border:none; background:transparent;
  color:var(--ink-faint); cursor:pointer;
  transition:background .15s ease, color .15s ease;
}
.card-dl-btn svg{ display:block; position:relative; }
.card-dl-btn:hover{ background:var(--accent-soft); color:var(--accent); }
.card-dl-btn:focus-visible{ outline:2px solid var(--accent); outline-offset:1px; }
.card-dl-btn::before{
  content:""; position:absolute; inset:0; border-radius:50%; opacity:0;
  background:conic-gradient(var(--accent) calc(var(--zip-p, 0) * 360deg), var(--line-strong) 0);
  -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2px));
          mask:radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2px));
}
.card-dl-btn.busy{ color:var(--accent); }
.card-dl-btn.busy::before{ opacity:1; }
.card-dl-btn.busy.pending::before{
  background:conic-gradient(var(--accent) 90deg, var(--line-strong) 0);
  animation:thumb-spin .8s linear infinite;
}
.card-dl-btn.failed{ color:#C2410C; }
.card-dl-btn.failed::before{ opacity:1; background:#C2410C; }
@media (prefers-reduced-motion: reduce){ .card-dl-btn.busy.pending::before{ animation:none; } }

/* ---------- multi-select (entered via press-and-hold, or via
   #topSelectBtn in .section-head; script: SELECT_HOLD_MS, topSelectBtn) ----------
   `.select-check`: corner tick, shown in select mode (always on a selected
   card, so it doesn't pop as the mode toggles). `#selectBar` below. */
.select-check{
  position:absolute; top:10px; left:10px; z-index:6;
  width:22px; height:22px; border-radius:50%;
  display:none; align-items:center; justify-content:center;
  border:2px solid rgba(255,255,255,.85);
  background:rgba(18,21,28,.28);
  backdrop-filter:blur(2px);
  transition:background .12s ease, border-color .12s ease;
}
.select-check svg{ opacity:0; transition:opacity .12s ease; }
#filesGrid.select-mode .select-check{ display:flex; }
.card.selected .select-check{
  display:flex;
  background:var(--accent); border-color:var(--accent);
}
.card.selected .select-check svg{ opacity:1; }
.card.selected{
  background:color-mix(in srgb, var(--accent) 10%, var(--surface));
  border-color:var(--accent);
  box-shadow:0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent);
}

/* Fixed to the viewport bottom so showing it never reflows the grid (the
   card under a holding finger must not jump). body.select-mode-active
   pads .main so the bar never covers the last row. */
#selectBar{
  /* No display here: an ID selector (1-0-0) would beat the browser's
     [hidden]{display:none}; see the :not([hidden]) rule below. */
  position:fixed; left:0; right:0; bottom:0; z-index:30;
  align-items:center; gap:10px;
  padding:10px 16px;
  padding-bottom:calc(10px + env(safe-area-inset-bottom, 0px));
  border-top:1px solid var(--line);
  background:var(--surface);
  box-shadow:0 -6px 24px rgba(18,21,28,.12);
}
#selectBar:not([hidden]){ display:flex; }
#selectBar span{ font-size:13px; font-weight:600; color:var(--ink); }
/* The count takes the free space, so buttons stay right-aligned even with Cut/Copy hidden. */
#selectCount{ margin-right:auto; }
body.select-mode-active .main{ padding-bottom:76px; }

/* Icon-only square buttons (#topSelectBtn, #selectBar's delete). */
.btn-icon{
  /* No display here (same as #selectBar): a class ties [hidden]'s
     specificity and author CSS wins, so display:inline-flex would keep
     #topSelectBtn visible while hidden. See :not([hidden]) below. */
  align-items:center; justify-content:center;
  width:34px; height:34px; padding:0; flex:none;
  border-radius:var(--radius-sm);
  border:1px solid transparent;
  cursor:pointer;
}
.btn-icon:not([hidden]){ display:inline-flex; }
/* 3-click cycle (see its handler). .active = mode on, not all picked
   (next = select all); .all-selected = all picked (next = exit). Hidden
   when the view is empty (renderFiles()). */
#topSelectBtn{
  border-color:var(--line-strong);
  background:transparent;
  color:var(--ink-soft);
}
#topSelectBtn:hover{ background:var(--bg); }
#topSelectBtn path{ opacity:0; transition:opacity .12s ease; }
#topSelectBtn.active{ border-color:var(--accent); color:var(--accent); }
#topSelectBtn.all-selected{ background:var(--accent); border-color:var(--accent); color:#fff; }
#topSelectBtn.all-selected path{ opacity:1; }
/* "Select" in words while select mode is off; in select mode the plain
   34px square. Hidden on the narrowest phones (like #newBtn's label). */
#topSelectBtn .sel-label{ display:none; }
#topSelectBtn:not(.active):not(.all-selected){ width:auto; gap:6px; padding:0 11px 0 9px; }
#topSelectBtn:not(.active):not(.all-selected) .sel-label{
  display:inline; font-family:var(--font-body); font-size:13.5px; font-weight:600; color:var(--ink);
}
/* #selectRenameBtn is always in the bar but dim + inert until exactly ONE
   item is picked (syncSelectionUI), then accent. */
#selectCutBtn, #selectCopyBtn{
  border-color:var(--line-strong); background:transparent; color:var(--ink-soft);
}
#selectCutBtn:hover, #selectCopyBtn:hover{ background:var(--bg); color:var(--ink); }
#selectRenameBtn{
  margin-left:0;
  border-color:var(--line-strong); background:transparent; color:var(--ink-soft);
  opacity:.4; pointer-events:none;
  transition:opacity .15s ease, color .15s ease, border-color .15s ease;
}
#selectRenameBtn:not(:disabled){
  opacity:1; pointer-events:auto;
  border-color:var(--accent); color:var(--accent);
}
#selectRenameBtn:not(:disabled):hover{ background:var(--bg); }
#selectRenameBtn:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
#selectDeleteBtn{ margin-left:0; }

/* ---------- collab (script: index-select.js grep "collab switch",
   index-cards.js grep "collab card") ----------
   Collab folder (write access via /c/<token>) = sky-blue card: tint, blue
   border + icon, "Collab folder" meta. .selected's accent look wins (the
   state being acted on), so these rules skip .selected.

   #collabCtl, after the count in #selectBar: the switch (a card's Public
   switch parts, blue; .mixed = some picks collab, knob centred) +
   #collabLinkBtn; only while an OWN folder is picked. #selectBar.has-collab
   moves the free space after the control so the count never shoves the
   switch (tabular digits; narrow screens drop " selected" and the switch
   label: a 4-digit count still fits 360px). */
.card.card-collab:not(.selected){
  background:color-mix(in srgb, var(--collab) 7%, var(--surface));
  border-color:color-mix(in srgb, var(--collab) 50%, var(--line));
}
.card.card-collab:not(.selected):hover{
  background:color-mix(in srgb, var(--collab) 12%, var(--surface));
  border-color:var(--collab);
}
.card.card-collab .file-icon{ background:var(--collab) !important; } /* beats the folder icon's inline grey */
.card.card-collab .card-meta{ color:var(--collab); font-weight:600; }
/* From the first collab folder in the path down (renderCrumbs): links lighter blue, current full. */
.crumbs a.crumb-collab{ color:color-mix(in srgb, var(--collab) 75%, var(--ink-faint)); }
.crumbs a.crumb-collab:hover{ color:var(--collab); }
.crumbs .current.crumb-collab{ color:var(--collab); }

/* A member's static "Collab" badge (collabBadgeHTML: shared footers + shared folder crumb). */
.vis-toggle.collab-badge{ color:var(--collab); cursor:default; gap:5px; padding-left:4px; }
.vis-toggle.collab-badge svg{ flex:none; }

#selectCount{ font-variant-numeric:tabular-nums; white-space:nowrap; }
#selectBar.has-collab #selectCount{ margin-right:0; }
.collab-ctl{ align-items:center; gap:4px; margin-right:auto; }
.collab-ctl:not([hidden]){ display:inline-flex; }
#selectBar .collab-toggle{
  border:0; background:none; font-family:inherit;
  font-size:12px; font-weight:600; color:var(--ink-faint);
  padding:5px 8px 5px 5px; min-height:34px;
}
#selectBar .collab-toggle:hover{ background:var(--collab-soft); }
#selectBar .collab-toggle:focus-visible{ outline:2px solid var(--collab); outline-offset:1px; }
#selectBar .collab-label{ font-size:12px; font-weight:600; color:inherit; font-variant-numeric:tabular-nums; white-space:nowrap; }
.collab-toggle.on, .collab-toggle.mixed{ color:var(--collab); }
.collab-toggle.on .switch{ background:var(--collab); }
.collab-toggle.on .switch::after{ left:14px; }
.collab-toggle.mixed .switch{ background:color-mix(in srgb, var(--collab) 55%, var(--line-strong)); }
.collab-toggle.mixed .switch::after{ left:8px; }
.collab-toggle.busy{ opacity:.65; pointer-events:none; }
.collab-toggle.busy .switch{ animation:collab-busy 1s ease-in-out infinite alternate; }
@keyframes collab-busy{ from{ opacity:1; } to{ opacity:.45; } }
@media (prefers-reduced-motion: reduce){ .collab-toggle.busy .switch{ animation:none; } }
#collabLinkBtn{ border-color:var(--line-strong); background:transparent; color:var(--ink-soft); }
#collabLinkBtn:not(:disabled):hover{ background:var(--collab-soft); border-color:var(--collab); color:var(--collab); }
#collabLinkBtn:not(:disabled){ color:var(--collab); border-color:color-mix(in srgb, var(--collab) 55%, var(--line-strong)); }
#collabLinkBtn:disabled{ opacity:.4; cursor:default; }
#collabLinkBtn:focus-visible{ outline:2px solid var(--collab); outline-offset:2px; }
#collabMembersBtn{ border-color:var(--line-strong); background:transparent; color:var(--ink-soft); }
#collabMembersBtn:not(:disabled){ color:var(--collab); border-color:color-mix(in srgb, var(--collab) 55%, var(--line-strong)); }
#collabMembersBtn:not(:disabled):hover{ background:var(--collab-soft); border-color:var(--collab); }
#collabMembersBtn:disabled{ opacity:.4; cursor:default; }
#collabMembersBtn:focus-visible{ outline:2px solid var(--collab); outline-offset:2px; }

/* Members dialog (#membersScrim, friendly-modal parts): scrollable list,
   Kick on the right (first click arms: .confirming, red). .members-note =
   "the link changed" + Copy new link, after a kick. */
.members-modal{ max-width:420px; display:flex; flex-direction:column; max-height:min(560px, calc(100vh - 40px)); }
.members-list{ list-style:none; margin:0 0 12px; padding:0; overflow-y:auto; min-height:0; }
.members-list:empty{ display:none; }
.member-row{ display:flex; align-items:center; gap:10px; padding:8px 0; border-top:1px solid var(--line); }
.member-row:first-child{ border-top:0; }
.member-main{ flex:1; min-width:0; }
.member-name{ font-size:13.5px; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.member-meta{ font-size:12px; color:var(--ink-faint); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.member-kick{ flex:none; padding:6px 12px; font-size:12.5px; }
.member-kick.confirming{ background:#B3261E; border-color:#B3261E; color:#fff; }
.member-kick:disabled{ opacity:.6; cursor:default; }
.members-note{
  display:flex; align-items:center; gap:10px; margin:0 0 14px; padding:10px 12px;
  border-radius:var(--radius-sm); background:var(--collab-soft); color:var(--collab);
  font-size:12.5px; font-weight:600; line-height:1.4;
}
.members-note[hidden]{ display:none; }
.members-note span{ flex:1; min-width:0; }
.members-note .btn{ flex:none; padding:6px 10px; font-size:12.5px; }

@media (max-width: 480px){
  #selectBar{ gap:6px; } /* room for the members button next to a 4-digit count */
  #selectBar .sel-word, #selectBar .collab-label{ display:none; }
  #selectBar .collab-toggle{ padding:5px; }
}

/* ---------- collab lock (owner: #collabLockBtn, index-select.js grep
   "collab lock"; member side: index-core.js "collab lock") ----------
   Locked = read-only for members. Owner: padlock in #collabCtl (open /
   closed + filled = locked / .mixed). Member: badge -> padlock "Locked"
   (.collab-badge.collab-locked), crumbs get .crumb-lock and, where nothing
   can be written, .crumb-locked (no drop-target look);
   html.collab-locked-view hides the upload area and its toggle. */
#collabLockBtn{ border-color:var(--line-strong); background:transparent; color:var(--ink-soft); }
#collabLockBtn:not(:disabled){ color:var(--collab); border-color:color-mix(in srgb, var(--collab) 55%, var(--line-strong)); }
#collabLockBtn:not(:disabled):hover{ background:var(--collab-soft); border-color:var(--collab); }
#collabLockBtn.locked:not(:disabled){ background:var(--collab); border-color:var(--collab); color:var(--surface); }
#collabLockBtn.locked:not(:disabled):hover{ background:color-mix(in srgb, var(--collab) 85%, #000); }
#collabLockBtn.mixed:not(:disabled){ background:color-mix(in srgb, var(--collab) 18%, transparent); }
#collabLockBtn:disabled{ opacity:.4; cursor:default; }
#collabLockBtn.busy{ opacity:.65; pointer-events:none; }
#collabLockBtn:focus-visible{ outline:2px solid var(--collab); outline-offset:2px; }
#collabLockBtn svg{ display:block; }

.vis-toggle.collab-badge.collab-locked{ color:var(--ink-soft); }
.card-meta .meta-lock{ display:inline-flex; vertical-align:-1px; margin-left:3px; }
.crumb-lock{ display:inline-flex; vertical-align:-1px; margin-left:4px; color:var(--collab); }
.crumbs.drop-ready a.crumb-locked, .crumbs.drop-near a.crumb-locked{
  color:color-mix(in srgb, var(--collab) 75%, var(--ink-faint));
  background:none; box-shadow:none; outline:none; animation:none;
}
html.collab-locked-view .dz-collapse, html.collab-locked-view .dz-toggle{ display:none; }
html.collab-locked-view .crumbs-row{ margin-bottom:0; }
#newBtn:disabled{ opacity:.45; cursor:not-allowed; }
/* Picks in a locked shared folder (Rename has its own dim look). */
#selectCutBtn:disabled, #selectDeleteBtn:disabled{ opacity:.4; cursor:not-allowed; }
#selectCutBtn:disabled:hover{ background:transparent; color:var(--ink-soft); }

/* ---------- collab tools menu (script: index-select.js grep "collab tools
   menu") ----------
   #collabTools = link / members / lock: an inline icon row normally.
   #collabCtl.compact (bar overflows, measured in JS): an upward menu above
   #collabMoreBtn like .new-pop, full-width labelled rows; .open shows it. */
.collab-ctl{ position:relative; }
.collab-tools{ display:inline-flex; align-items:center; gap:4px; }
.collab-tools .ct-label{ display:none; }
.collab-tools .ct-icon{ display:inline-flex; }
.collab-tools .ct-icon svg{ display:block; }
#collabMoreBtn{ border-color:color-mix(in srgb, var(--collab) 55%, var(--line-strong)); background:transparent; color:var(--collab); }
#collabMoreBtn:hover, #collabMoreBtn[aria-expanded="true"]{ background:var(--collab-soft); border-color:var(--collab); }
#collabMoreBtn:focus-visible{ outline:2px solid var(--collab); outline-offset:2px; }
.collab-ctl.compact .collab-tools{
  position:absolute; bottom:calc(100% + 12px); left:0; z-index:31;
  display:none; flex-direction:column; align-items:stretch; gap:2px;
  min-width:180px; padding:6px;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--radius-md);
  box-shadow:0 -12px 32px rgba(18,21,28,.16);
}
.collab-ctl.compact .collab-tools.open{ display:flex; }
/* The bar (z 30) sits below the FABs (z 60) where the menu opens: lift it while open. */
#selectBar.collab-menu-open{ z-index:61; }
.collab-ctl.compact .collab-tools .btn-icon{
  width:100%; height:auto; justify-content:flex-start; gap:10px;
  padding:9px 10px; border:none; border-radius:var(--radius-sm);
  background:none; font:inherit; font-size:14px; font-weight:500; white-space:nowrap;
}
.collab-ctl.compact .collab-tools .btn-icon:not(:disabled){ color:var(--collab); }
.collab-ctl.compact .collab-tools .btn-icon:not(:disabled):hover,
.collab-ctl.compact .collab-tools .btn-icon:focus-visible{ background:var(--collab-soft); outline:none; }
#selectBar .collab-ctl.compact .collab-tools .ct-label{ display:inline; font-size:14px; font-weight:500; color:var(--ink); } /* beats "#selectBar span" */
#selectBar .collab-ctl.compact .collab-tools .btn-icon:disabled .ct-label{ color:var(--ink-faint); }
/* Locked in the menu: tinted row, not the inline solid fill. */
.collab-ctl.compact #collabLockBtn.locked:not(:disabled){ background:var(--collab-soft); color:var(--collab); }
.collab-ctl.compact #collabLockBtn.locked:not(:disabled):hover{ background:color-mix(in srgb, var(--collab) 22%, transparent); }

/* ---------- cut/copy clipboard (paste + cancel replace #topSelectBtn) ----------
   While state.clipboard is set (syncClipboardUI): #pasteBtn +
   #pasteCancelBtn replace #topSelectBtn; clipboard cards fade with a big
   scissors / copy icon. `.btn` sets display:inline-flex, so these [hidden]
   rules are what hide them. */
#pasteBtn[hidden], #pasteCancelBtn[hidden]{ display:none; }
#pasteBtn:disabled{ opacity:.45; cursor:not-allowed; }
#pasteCancelBtn:disabled{ opacity:.45; cursor:not-allowed; }
.card.clip-cut, .card.clip-copy{ border-style:dashed; border-color:var(--accent); }
.card.clip-cut > *:not(.clip-badge), .card.clip-copy > *:not(.clip-badge){ opacity:.38; }
.clip-badge{
  position:absolute; inset:0; z-index:2;
  display:flex; align-items:center; justify-content:center;
  pointer-events:none; color:var(--accent);
}
.clip-badge svg{
  width:64px; height:64px; padding:14px; box-sizing:border-box;
  border-radius:50%; background:color-mix(in srgb, var(--surface) 88%, transparent);
  box-shadow:0 4px 14px rgba(18,21,28,.22);
}

/* ---------- drag-to-move (pointer-events based so it works with touch,
   not just the HTML5 DnD API which phones don't support) ----------
   pointerdown arms a card (mouse at once, touch/pen after a short hold so
   scrolling works); past a small threshold a tilted, wiggling "ghost"
   clone spawns; the source dims in place; a folder card / crumb under it
   highlights as the target; release PATCHes the move, or the ghost springs
   back. Logic: index-gestures.js "drag-to-move".
   The quick horizontal card swipe is a different gesture that reuses
   `.card-move-out` (card swipe, index-gestures.js). */
.card.drag-source{ opacity:.35; }
@keyframes card-armed-pulse{
  0%{ transform:scale(1); box-shadow:none; }
  45%{ transform:scale(1.045); box-shadow:0 10px 20px rgba(18,21,28,.16); }
  100%{ transform:scale(1.018); box-shadow:0 6px 14px rgba(18,21,28,.12); }
}
.card.card-armed{ animation:card-armed-pulse .22s ease forwards; }
.card.card-move-out{ transition:transform .16s ease, opacity .16s ease; transform:scale(.82); opacity:0; }
@keyframes folder-receive{
  0%{ transform:scale(1); }
  35%{ transform:scale(.92); }
  65%{ transform:scale(1.07); }
  100%{ transform:scale(1); }
}
.card.drop-target{
  background:color-mix(in srgb, var(--accent) 14%, var(--surface));
  border-color:var(--accent);
  box-shadow:0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent);
}
.card.drop-received{ animation:folder-receive .32s cubic-bezier(.3,.7,.4,1.4); }
/* Crumbs as drop targets (script: grep updateGhostFade, crumbHitNear),
   three steps, no layout change (background + box-shadow + outline):
     .crumbs.drop-ready  a drag is on and there's an ancestor to drop on:
                         soft dashed pills (short pulse);
     .crumbs.drop-near   card close to the bar: stronger pills;
     a.drop-target       the landing crumb: solid accent, a bit bigger. */
.crumbs a[data-crumb]{ border-radius:5px; transition:background .15s ease, box-shadow .15s ease, color .15s ease, transform .15s ease, outline-color .15s ease; }
.crumbs.drop-ready a[data-crumb]{
  color:var(--accent);
  background:color-mix(in srgb, var(--accent) 9%, transparent);
  box-shadow:0 0 0 5px color-mix(in srgb, var(--accent) 9%, transparent);
  outline:1.5px dashed color-mix(in srgb, var(--accent) 55%, transparent); outline-offset:5px;
  animation:crumb-ready-pulse .5s ease 2;
}
.crumbs.drop-near a[data-crumb]{
  background:color-mix(in srgb, var(--accent) 18%, transparent);
  box-shadow:0 0 0 6px color-mix(in srgb, var(--accent) 18%, transparent);
  outline-color:var(--accent);
}
.crumbs a[data-crumb].drop-target{
  color:#fff; font-weight:600;
  background:var(--accent);
  box-shadow:0 0 0 6px var(--accent), 0 4px 16px color-mix(in srgb, var(--accent) 45%, transparent);
  outline-color:transparent;
  transform:scale(1.08);
  animation:none;
}
@keyframes crumb-ready-pulse{ 50%{ transform:scale(1.08); } }
@media (prefers-reduced-motion: reduce){ .crumbs.drop-ready a[data-crumb]{ animation:none; } .crumbs a[data-crumb].drop-target{ transform:none; } }
/* Mouse: solid, see-through within DRAG_CRUMBS_FADE_MARGIN of the crumbs
   (don't hide the highlight). Touch/pen (.touch-drag): the card sits on the
   target, so always see-through, more over a folder (.over-target), most
   near the crumbs. Classes from updateGhostFade / updateDropTarget. Order
   matters: later rules win at equal specificity. */
.drag-ghost{
  position:fixed; top:0; left:0; z-index:1000;
  pointer-events:none; will-change:transform;
  transition:opacity .15s ease;
}
.drag-ghost.touch-drag{ opacity:.65; }
.drag-ghost.touch-drag.over-target{ opacity:.5; }
.drag-ghost.near-crumbs, .drag-ghost.touch-drag.near-crumbs{ opacity:.4; } /* second selector: outranks .touch-drag.over-target */
.drag-ghost-inner{
  width:100%; height:100%; box-sizing:border-box;
  background:var(--surface); border:1px solid var(--line-strong);
  border-radius:var(--radius-md); padding:14px;
  display:flex; flex-direction:column; gap:10px; overflow:hidden;
  box-shadow:0 20px 44px rgba(18,21,28,.30);
  transform-origin:center center;
  animation:drag-wiggle .8s ease-in-out infinite;
}
@keyframes drag-wiggle{
  0%,100%{ transform:rotate(-4deg) scale(1.06); }
  50%{ transform:rotate(-7.5deg) scale(1.06); }
}

/* ---------- swipe indicator badges (move-out / delete) ----------
   Only during a card swipe (spawnSwipeIndicator), fixed at the card's
   un-translated corner (revealed under the sliding ghost). Grows toward
   CARD_SWIPE_COMMIT_DISTANCE; opacity/scale/`.armed` set by JS per
   pointermove, not transitions (they'd lag the finger). */
.swipe-indicator{
  position:fixed; z-index:999;
  width:34px; height:34px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:var(--surface); border:2px solid var(--line-strong);
  color:var(--ink-faint);
  opacity:0; transform:scale(.6);
  pointer-events:none;
  box-shadow:0 4px 12px rgba(18,21,28,.18);
}
.swipe-indicator.armed{
  background:var(--accent); border-color:var(--accent); color:#fff;
}
.swipe-indicator.swipe-indicator-delete.armed{ background:#B3261E; border-color:#B3261E; }
:root[data-theme="dark"] .swipe-indicator.swipe-indicator-delete.armed{ background:#7A2B25; border-color:#7A2B25; }
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .swipe-indicator.swipe-indicator-delete.armed{ background:#7A2B25; border-color:#7A2B25; }
}

/* ---------- custom scroll indicator (touch devices only) ----------
   Cards are touch-action:none, so swipes starting on them scroll via JS
   (window.scrollBy) and Chrome paints no overlay scrollbar. Stand-in: a thin
   thumb shown on any 'scroll' event, fading ~500ms after. Coarse pointers
   only; desktop keeps its native scrollbar. */
@media (pointer: coarse){
  html{ scrollbar-width:none; }
  html::-webkit-scrollbar{ display:none; }
}
.scroll-indicator{
  position:fixed; right:2px; top:0;
  width:4px; border-radius:99px;
  background:var(--ink-faint);
  opacity:0; pointer-events:none;
  transition:opacity .25s ease;
  z-index:200;
  transform:translateY(0);
}
.scroll-indicator.visible{ opacity:.55; }

/* ---------- back-to-top button ---------- */
.scroll-top-btn{
  position:fixed; right:20px; z-index:60;
  bottom:calc(20px + env(safe-area-inset-bottom, 0px));
  width:44px; height:44px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:var(--surface); color:var(--ink-soft);
  border:1px solid var(--line-strong);
  box-shadow:0 6px 18px rgba(18,21,28,.18);
  cursor:pointer;
  opacity:0; pointer-events:none; transform:translateY(8px);
  transition:opacity .18s ease, transform .18s ease;
}
.scroll-top-btn.visible{ opacity:1; pointer-events:auto; transform:translateY(0); }
.scroll-top-btn:hover{ background:var(--bg); color:var(--ink); }
.scroll-top-btn:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
/* Lifted above #selectBar while it's up. */
body.select-mode-active .scroll-top-btn{ bottom:calc(86px + env(safe-area-inset-bottom, 0px)); }

/* (No refresh / back-to-parent buttons: reload keeps the view via the URL,
   Back works since folder navigation pushes history.) */
.select-cancel-fab{
  display:none;
  position:fixed; right:132px; z-index:60;
  bottom:calc(86px + env(safe-area-inset-bottom, 0px)); /* the select bar is always up while this shows */
  width:44px; height:44px; border-radius:50%;
  align-items:center; justify-content:center;
  background:var(--surface); color:var(--ink-soft);
  border:1px solid var(--line-strong);
  box-shadow:0 6px 18px rgba(18,21,28,.18);
  cursor:pointer;
}
.select-cancel-fab:hover{ background:var(--bg); color:var(--ink); }
.select-cancel-fab:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
body.select-mode-active .select-cancel-fab{ display:flex; }

/* ---------- go-to-location button ----------
   44px circle like the other FABs. Shown (.show, syncSelectionUI) only with
   exactly ONE pick, so always in select mode: the 86px offset is built in.
   Row: select-cancel (132px) -> goto (76px) -> top (20px) at every width
   (20 + 44 + 12 = 76; 76 + 44 + 12 = 132). Folder -> it, file -> its folder. */
.goto-fab{
  display:flex;
  position:fixed; right:76px; z-index:60;
  bottom:calc(86px + env(safe-area-inset-bottom, 0px));
  width:44px; height:44px; border-radius:50%;
  align-items:center; justify-content:center;
  background:var(--surface); color:var(--ink-soft);
  border:1px solid var(--line-strong);
  box-shadow:0 6px 18px rgba(18,21,28,.18);
  cursor:pointer;
  opacity:0; pointer-events:none; transform:translateY(8px);
  transition:opacity .18s ease, transform .18s ease;
}
.goto-fab.show{ opacity:1; pointer-events:auto; transform:translateY(0); }
.goto-fab:hover{ background:var(--bg); color:var(--ink); }
.goto-fab:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.goto-fab.busy{ pointer-events:none; opacity:.7; }

/* Cancel cut/copy FAB (#clipCancelFabBtn): takes go-to's slot while the
   clipboard is set (select mode is off then, so no select bar: 20px baseline). */
.goto-fab.clip-cancel-fab{
  bottom:calc(20px + env(safe-area-inset-bottom, 0px));
  color:var(--accent); border-color:var(--accent);
}

/* ---------- friendly modal (replaces window.prompt / window.confirm) ---------- */
.modal-scrim{
  position:fixed; inset:0;
  background:rgba(15,17,21,.45);
  display:none;
  align-items:center; justify-content:center;
  padding:20px;
  /* Above .drag-ghost (1000) and .swipe-indicator (999): a right-swipe
     delete freezes the card behind this scrim while it asks. */
  z-index:1010;
}
.modal-scrim.open{ display:flex; }
.modal{
  width:100%; max-width:380px;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--radius-lg);
  box-shadow:0 24px 60px rgba(18,21,28,.26);
  padding:20px;
}
.modal h3{ margin:0 0 8px; font-family:var(--font-brand); font-size:17px; font-weight:700; }
.modal p{ margin:0 0 16px; font-size:13.5px; color:var(--ink-soft); line-height:1.5; }
.modal input{
  width:100%; padding:9px 11px; margin-bottom:16px;
  border:1px solid var(--line); border-radius:var(--radius-sm);
  background:var(--bg); color:var(--ink); font-family:inherit; font-size:14px;
}
.modal input:focus{ outline:2px solid var(--accent); outline-offset:1px; }
.modal-actions{ display:flex; justify-content:flex-end; flex-wrap:wrap; gap:8px; }
/* #modalExtra pinned left; on narrow phones Cancel + Confirm wrap under it. */
.modal-actions .modal-extra{ margin-right:auto; }
.modal-actions .modal-extra[hidden]{ display:none; } /* .btn's display would beat [hidden] */
@media (max-width: 480px){ .modal-actions .modal-extra{ flex-basis:100%; justify-content:center; margin-right:0; } }
/* requireText: Confirm waits for the typed word. */
.modal-actions .btn:disabled{ opacity:.45; cursor:not-allowed; }
.btn-danger{ background:#B3261E; color:#fff; }
.btn-danger:hover{ background:#8f1e17; }
:root[data-theme="dark"] .btn-danger{ background:#7A2B25; }
:root[data-theme="dark"] .btn-danger:hover{ background:#8f342c; }
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .btn-danger{ background:#7A2B25; }
  :root:not([data-theme="light"]) .btn-danger:hover{ background:#8f342c; }
}

.empty-hint{
  grid-column:1/-1; padding:34px 20px; text-align:center;
  color:var(--ink-faint); font-size:13.5px;
  border:1.5px dashed var(--line); border-radius:var(--radius-lg);
}
.unselectable {
  user-select: none;
  -webkit-user-select: none; /* Safari */
  -moz-user-select: none;    /* Firefox */
  -ms-user-select: none;     /* Older IE/Edge */
}
.copy-link{
  font-size:11px; font-weight:600; color:var(--public); background:none; border:none;
  cursor:pointer; padding:0; font-family:inherit; margin-top:-2px;
}
.copy-link:hover{ text-decoration:underline; }
.copy-link-sep{ font-size:11px; color:var(--ink-faint); margin:0 6px; }

.site-footer{
  margin-top:auto;
  padding-top:16px;
  border-top:1px solid var(--line);
  display:flex;
  flex-wrap:wrap;
  justify-content:center;
  gap:8px 24px;
  font-size:12.5px;
  color:var(--ink-faint);
}
.site-footer a{ color:var(--ink-faint); }
.site-footer a:hover{ color:var(--ink); text-decoration:underline; }

/* ---------- navigation progress bar + lock ----------
   runNavigation(). Bar width animates via transform (compositor-only:
   smooth on a busy main thread). The lock is an invisible full-screen layer
   swallowing pointer input; above rail/FABs (z 50-60), below drag ghost
   (1000) and modal (1010), which can't be open then. */
.nav-bar{
  position:fixed; top:0; left:0; right:0; height:3px; z-index:1100;
  pointer-events:none; background:var(--accent);
  transform-origin:0 50%; transform:scaleX(0); opacity:0;
  will-change:transform, opacity;
}
.nav-bar.fail{ background:#D6453D; }
.nav-lock{ position:fixed; inset:0; z-index:900; display:none; cursor:progress; }
.nav-lock.on{ display:block; }

/* ---------- upbin viewer ----------
   Full-screen viewer for image / video / audio / text (script: grep "upbin
   viewer"); own --uv-* tokens following the page theme.
   Column: .uv-bar on top, .uv-stage below; media is object-fit:contain
   INSIDE the stage, so nothing covers the bar / close button.
   Ways out, deliberately few: X, Escape, Back (history entry). No swipe
   to close, no click-outside. Left/right: big side zones (.uv-nav), arrow
   keys, horizontal swipe. touch-action:none: the script owns every stage
   gesture (no pinch-zoom).
   Film strip: .uv-track holds 3 .uv-slide (prev / current / next, placed
   by the script) and moves as one, so the neighbour follows the finger.
   Video slides: upload-time thumbnail poster + big .uv-play; pressing it or
   tapping the frame (even under the zones; only their round icons step)
   turns on native controls and plays. While the CURRENT slide plays
   (.uv-playing) the zones go away (they'd cover the controls) and gestures
   starting ON the video are its own; swipes from around it and arrows still
   step (stopping it). */
/* Viewer palette follows the page theme (same selectors as the page tokens).
   --uv-veil = RGB of faint stage overlays (white on dark, ink on light).
   --uv-disc* = round prev/next icons (frosted dark / frosted white).
   --uv-scheme = color-scheme for native controls / scrollbars. The video
   play button (.uv-play, .uv-tile-play) stays dark: it sits on a picture. */
:root{
  --uv-bg:#F3F4F7; --uv-ink:#12151C; --uv-ink-soft:#5B6270;
  --uv-btn:rgba(18,21,28,.07); --uv-btn-hover:rgba(18,21,28,.14);
  --uv-veil:18 21 28; --uv-scheme:light;
  --uv-disc:rgba(255,255,255,.8); --uv-disc-hover:rgba(255,255,255,.96); --uv-disc-ink:#12151C; --uv-disc-shadow:0 2px 10px rgba(18,21,28,.16);
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --uv-bg:rgb(10,12,16); --uv-ink:#F1F2F5; --uv-ink-soft:#A7ACBA;
    --uv-btn:rgba(255,255,255,.12); --uv-btn-hover:rgba(255,255,255,.24);
    --uv-veil:255 255 255; --uv-scheme:dark;
    --uv-disc:rgba(10,12,16,.45); --uv-disc-hover:rgba(10,12,16,.75); --uv-disc-ink:#F1F2F5; --uv-disc-shadow:none;
  }
}
:root[data-theme="dark"]{
  --uv-bg:rgb(10,12,16); --uv-ink:#F1F2F5; --uv-ink-soft:#A7ACBA;
  --uv-btn:rgba(255,255,255,.12); --uv-btn-hover:rgba(255,255,255,.24);
  --uv-veil:255 255 255; --uv-scheme:dark;
  --uv-disc:rgba(10,12,16,.45); --uv-disc-hover:rgba(10,12,16,.75); --uv-disc-ink:#F1F2F5; --uv-disc-shadow:none;
}
html.viewer-open, html.viewer-open body{ overflow:hidden; }
.uv{
  position:fixed; inset:0; z-index:960;              /* above the grid UI, ghost-free (no drag can run under it); below .modal-scrim (1010) */
  display:none; flex-direction:column;
  background:var(--uv-bg); color:var(--uv-ink); color-scheme:var(--uv-scheme);
  touch-action:none; user-select:none; -webkit-user-select:none;
}
.uv.open{ display:flex; }
/* ---------- uv now playing ----------
   The topbar sticker again in the viewer bar (#uvNowPlaying; the viewer
   covers the topbar). Script: grep "uv now playing". Shown while a track
   is loaded EXCEPT on that track's own slide in single view; always in
   grid / manga. Only colours change (--uv-*). After the topbar sticker's
   rules so these win its phone block. */
.uv-np{
  width:220px;
  background:color-mix(in srgb, var(--accent) 10%, var(--uv-bg));
  border-color:color-mix(in srgb, var(--accent) 35%, rgb(var(--uv-veil) / .14));
  color:var(--uv-ink);
}
.uv-np .np-open{ border-right-color:color-mix(in srgb, var(--accent) 25%, rgb(var(--uv-veil) / .12)); }
.uv-np:not(.playing) .np-wave{ color:var(--uv-ink-soft); }
@media (max-width: 1040px){ .uv-np{ width:190px; } }
@media (max-width: 760px){
  .uv-np{ flex:0 1 170px; width:auto; }
  .uv-np ~ .uv-count{ display:none; } /* the strip's "3 / 12" makes room for the sticker on a phone */
}
.uv-bar{
  flex:none; display:flex; align-items:center; gap:12px;
  padding:10px 12px 10px 16px;
  padding-top:calc(10px + env(safe-area-inset-top, 0px));
  position:relative; z-index:2;
}
.uv-title{ flex:1; min-width:0; font-size:14px; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* uv title rename (script: grep "uv title rename" in upbin-viewer.js;
   index.html only, >= VIEWER_RENAME_MIN_PX): at rest the title with a faint
   hover pill; focused a text field (ring, no ellipsis). Saving = faded.
   flex:0 1 auto + margin-right:auto: the pill hugs the name. */
.uv-title.uv-title-edit{
  flex:0 1 auto; margin:0 auto 0 -8px; padding:5px 8px; border-radius:var(--radius-sm);
  cursor:text; outline:none; user-select:text; -webkit-user-select:text;
  transition:background .15s ease, box-shadow .15s ease;
}
.uv-title.uv-title-edit:hover{ background:var(--uv-btn); }
.uv-title.uv-title-edit:focus{
  background:var(--uv-bg); text-overflow:clip;
  box-shadow:inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 70%, transparent);
}
.uv-title.uv-title-saving{ opacity:.6; }
.uv-count{ flex:none; font-size:12.5px; color:var(--uv-ink-soft); font-variant-numeric:tabular-nums; }
.uv-btn{
  flex:none; width:40px; height:40px; border-radius:50%;
  display:inline-flex; align-items:center; justify-content:center;
  border:none; background:var(--uv-btn); color:var(--uv-ink); cursor:pointer;
  transition:background .15s ease, opacity .15s ease;
}
.uv-btn:hover, .uv-btn:focus-visible{ background:var(--uv-btn-hover); }
.uv-stage{
  flex:1; min-height:0; position:relative; overflow:hidden;
  padding:0 0 env(safe-area-inset-bottom, 0px);
}
.uv-track{ position:absolute; inset:0; will-change:transform; }
.uv-slide{ position:absolute; inset:0; }
.uv-slide[hidden]{ display:none; }
.uv-img{
  position:absolute; inset:0; margin:auto;
  max-width:100%; max-height:100%; width:auto; height:auto;
  object-fit:contain; -webkit-user-drag:none;
}
.uv-img.uv-hidden{ visibility:hidden; }
/* Play button (also .card-play): frosted dark disc + ring + drop shadow
   (reads on white frames). The triangle's CENTROID is at the viewBox
   centre (12,12), which looks centred: no margin nudge. Corners rounded by
   a same-colour round-join stroke (keeps the centroid). */
.uv-play{
  position:absolute; left:50%; top:50%; width:76px; height:76px; margin:-38px 0 0 -38px;
  border-radius:50%; border:none; padding:0; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  background:rgba(10,12,16,.5); color:#F1F2F5; /* dark in both themes: sits on the poster */
  -webkit-backdrop-filter:blur(10px) saturate(140%); backdrop-filter:blur(10px) saturate(140%);
  box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.28), 0 6px 24px rgba(0,0,0,.35);
  -webkit-tap-highlight-color:transparent;
  transition:background .15s ease, transform .15s ease, box-shadow .15s ease;
}
.uv-play[hidden]{ display:none; }
.uv-slide video.uv-img:not([controls]){ cursor:pointer; } /* a tap on the paused frame plays it (tapHitsPausedVideo) */
.uv-play svg{ display:block; width:34px; height:34px; }
@media (hover:hover){ .uv-play:hover{ background:rgba(10,12,16,.72); box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.45), 0 8px 28px rgba(0,0,0,.45); transform:scale(1.06); } }
.uv-play:active{ transform:scale(.96); }
.uv-play:focus-visible{ outline:none; box-shadow:inset 0 0 0 2px var(--uv-ink), 0 0 0 2px var(--uv-ink); }
.uv.uv-playing .uv-nav{ display:none; }
/* Prev / next zones: full height, 30% of the width each side (over the
   image too). At either end .uv-off: no icon, no-op. */
.uv-nav{
  position:absolute; top:0; bottom:0; z-index:1;
  width:30%; min-width:64px; padding:0 12px;
  display:flex; align-items:center;
  border:none; background:transparent; color:var(--uv-ink);
  cursor:pointer; -webkit-tap-highlight-color:transparent;
  transition:background .15s ease;
}
.uv-prev{ left:0; justify-content:flex-start; }
.uv-next{ right:0; justify-content:flex-end; }
.uv-nav-icon{
  width:44px; height:44px; border-radius:50%; flex:none;
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--uv-disc); color:var(--uv-disc-ink); box-shadow:var(--uv-disc-shadow);
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  transition:background .15s ease, opacity .15s ease, transform .15s ease;
}
@media (hover:hover){
  .uv-prev:not(.uv-off):hover{ background:linear-gradient(to right, rgb(var(--uv-veil) / .07), transparent); }
  .uv-next:not(.uv-off):hover{ background:linear-gradient(to left, rgb(var(--uv-veil) / .07), transparent); }
  .uv-nav:not(.uv-off):hover .uv-nav-icon{ background:var(--uv-disc-hover); transform:scale(1.08); }
}
.uv-nav:focus-visible{ outline:none; }
.uv-nav:focus-visible .uv-nav-icon{ box-shadow:0 0 0 2px var(--uv-ink); }
.uv-nav.uv-off{ cursor:default; }
.uv-nav.uv-off .uv-nav-icon{ opacity:0; }
.uv-msg{ position:absolute; inset:0; display:flex; flex-direction:column; gap:12px; align-items:center; justify-content:center; text-align:center; padding:20px; font-size:14px; color:var(--uv-ink-soft); pointer-events:none; }
.uv-msg[hidden]{ display:none; }
/* Audio slide (script: grep "audio slide"): art square (waveform from the
   file id, dances while sounding), name, and the shared <audio controls>
   once it's the background track. A tap in the MIDDLE strip (between the
   zones) plays / pauses (tapHitsAudioSlide); zones step. .uv-audio-play
   shows whenever not sounding. Above the zones (z 2) but click-through
   except the play button and the player. */
.uv-audio{ position:absolute; inset:0; pointer-events:none; display:flex; flex-direction:column; gap:18px; align-items:center; justify-content:center; padding:24px; text-align:center; color:var(--uv-ink); }
.uv-audio[hidden]{ display:none; }
.uv-audio-art{
  --aud:#B8560B;
  position:relative; flex:none; overflow:hidden;
  /* Fits the middle 40% (zones are 30% a side). */
  width:clamp(112px, min(34vw, 34vh), 240px); aspect-ratio:1; border-radius:22px;
  container-type:inline-size;
  display:flex; align-items:center; justify-content:center;
  background:linear-gradient(140deg, color-mix(in srgb, var(--aud) 30%, var(--uv-bg)), color-mix(in srgb, var(--aud) 10%, var(--uv-bg)));
  color:color-mix(in srgb, var(--aud) 72%, var(--uv-ink));
  box-shadow:0 10px 36px rgba(0,0,0,.18), inset 0 0 0 1px rgb(var(--uv-veil) / .08);
  transition:box-shadow .2s ease;
}
.uv-audio.is-current .uv-audio-art{ box-shadow:0 10px 36px rgba(0,0,0,.18), inset 0 0 0 2px color-mix(in srgb, var(--aud) 70%, transparent); }
.uv-audio-note{
  position:absolute; top:9%; right:9%; width:clamp(24px, 14cqw, 32px); height:clamp(24px, 14cqw, 32px); border-radius:9px;
  display:flex; align-items:center; justify-content:center;
  background:color-mix(in srgb, var(--aud) 85%, #000); color:#fff;
}
.uv-audio-wave{ display:flex; align-items:center; justify-content:center; gap:3px; width:100%; height:46%; padding:0 12%; opacity:.45; transition:opacity .2s ease; }
.uv-audio-wave i{ flex:1; max-width:6px; height:var(--h); border-radius:99px; background:currentColor; transform-origin:center; }
.uv-audio.is-current .uv-audio-wave{ opacity:.7; }
.uv-audio.is-playing .uv-audio-wave{ opacity:.9; }
.uv-audio.is-playing .uv-audio-wave i{ animation:card-wave .9s ease-in-out infinite alternate; animation-delay:var(--d); }
@media (prefers-reduced-motion: reduce){ .uv-audio.is-playing .uv-audio-wave i{ animation:none; } }
.uv-audio-name{ font-size:15px; font-weight:600; max-width:min(520px, 100%); word-break:break-word; }
.uv-audio audio{ width:min(520px, 100%); pointer-events:auto; }
/* No own <audio>: the ONE background player (cardAudio.el, grep "card
   audio") moves in while its track is on a slide (same playback). */
.uv-audio-player{ display:contents; }
.uv-audio .uv-audio-play{ --pb:clamp(52px, 32cqw, 76px); pointer-events:auto; width:var(--pb); height:var(--pb); margin:calc(var(--pb) / -2) 0 0 calc(var(--pb) / -2); }
.uv-audio .uv-audio-play svg{ width:45%; height:45%; }
.uv-audio .uv-audio-play[hidden]{ display:none; }
/* .uv-audio-on (showViewerImage): the track (own stacking context) is
   lifted above the zones but click-through, so only the play button and
   <audio> take clicks; elsewhere taps reach zones / stage. */
.uv.uv-audio-on .uv-stage{ cursor:pointer; }
.uv.uv-audio-on .uv-track{ z-index:2; pointer-events:none; }
.uv.uv-audio-on .uv-msg button, .uv.uv-audio-on .uv-msg a{ pointer-events:auto; }
/* text slide (script: grep "text slide" in upbin-viewer.js; panel built
   by the script): scrollable monospace "page". .uv-text-on lifts the track
   above the zones like audio (panel gets clicks / selection / wheel; zones
   usable beside a narrow panel). touch-action:pan-y: vertical scrolls the
   panel, horizontal still swipes. */
.uv-text{
  position:absolute; inset:12px 12px calc(12px + env(safe-area-inset-bottom, 0px)); margin:0 auto; max-width:820px;
  overflow:auto; overscroll-behavior:contain; -webkit-overflow-scrolling:touch;
  padding:18px 20px; border-radius:var(--radius-md);
  background:color-mix(in srgb, var(--uv-bg) 95%, var(--uv-ink)); border:1px solid rgb(var(--uv-veil) / .1); color:var(--uv-ink); /* opaque: the icons under it must not show through */
  font:13.5px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  white-space:pre-wrap; overflow-wrap:anywhere; tab-size:4;
  user-select:text; -webkit-user-select:text; touch-action:pan-y; outline:none;
}
.uv-text[hidden]{ display:none; }
.uv-text:focus-visible{ border-color:rgb(var(--uv-veil) / .35); }
.uv-text:empty::before{ content:"Empty file"; color:var(--uv-ink-soft); font-family:var(--font-body); }
.uv.uv-text-on .uv-track{ z-index:2; pointer-events:none; }
@media (max-width: 860px){ .uv.uv-text-on .uv-nav-icon{ visibility:hidden; } } /* panel ~full width would clip the round icons; swipe / arrows step */
.uv.uv-text-on .uv-text, .uv.uv-text-on .uv-msg button, .uv.uv-text-on .uv-msg a{ pointer-events:auto; }
/* text edit (index.html only: upbin-viewer.js grep "text edit", page side
   index-notes.js): a <textarea>, same look. It needs an explicit size
   (intrinsic sizing); centred by left/right + margin:auto. Accent caret;
   read-only ones (locked folder, not UTF-8) look exactly like the div. */
textarea.uv-text{
  display:block; box-sizing:border-box; resize:none; margin:0 auto;
  width:calc(100% - 24px); height:calc(100% - 24px - env(safe-area-inset-bottom, 0px));
  caret-color:var(--accent);
}
textarea.uv-text[hidden]{ display:none; }
textarea.uv-text[readonly]{ caret-color:transparent; }
textarea.uv-text:not([readonly]):focus{ border-color:color-mix(in srgb, var(--accent) 55%, transparent); }
textarea.uv-text::placeholder{ color:var(--uv-ink-soft); font-family:var(--font-body); opacity:1; }
/* uv save: #uvSave (left of Download; index-notes.js grep "uv save").
   Faded with nothing to save, accent when dirty, brief check mark once saved. */
.uv-save .uv-saved-ico{ display:none; }
.uv-save.saved .uv-save-ico{ display:none; }
.uv-save.saved .uv-saved-ico{ display:inline; }
.uv-save[aria-disabled="true"]{ opacity:.5; cursor:default; }
.uv-save[aria-disabled="true"]:hover{ background:var(--uv-btn); }
.uv-save.saved{ opacity:1; color:var(--accent); }
.uv-save.dirty{ background:var(--accent); color:#fff; }
.uv-save.dirty:hover, .uv-save.dirty:focus-visible{ background:color-mix(in srgb, var(--accent) 85%, #000); }
.uv-save.busy{ opacity:.6; cursor:progress; }
/* Grid mode (script: grep "uv grid"): the same list as square tiles,
   replacing the stage (display:none; slots stay loaded for an instant
   return). Own scroll box: touch-action pan-y re-allows vertical pan,
   overscroll-behavior keeps the page still. object-fit:cover; current item
   = inset ring on ::after (the image covers the tile). Videos: thumbnail +
   small play mark. */
.uv.uv-grid-mode .uv-stage{ display:none; }
.uv-btn[aria-pressed="true"]{ background:var(--uv-ink); color:var(--uv-bg); }
.uv-btn[hidden]{ display:none; }
.uv-grid{
  flex:1; min-height:0; overflow-y:auto; overscroll-behavior:contain;
  touch-action:pan-y; -webkit-overflow-scrolling:touch;
  display:grid; grid-template-columns:repeat(auto-fill, minmax(150px, 1fr));
  gap:4px; align-content:start;
  grid-auto-rows:max-content; /* NOT auto: with a fixed-height grid, overflow:hidden tiles' minimum is 0, so auto rows squashed instead of scrolling */
  padding:4px 12px calc(16px + env(safe-area-inset-bottom, 0px));
  animation:uv-fade-in .16s ease;
}
.uv-grid[hidden]{ display:none; }
@keyframes uv-fade-in{ from{ opacity:0; } to{ opacity:1; } }
@media (prefers-reduced-motion: reduce){ .uv-grid{ animation:none; } }
.uv-tile{
  position:relative; aspect-ratio:1; overflow:hidden;
  border:none; padding:0; margin:0; border-radius:4px; cursor:pointer;
  background:rgb(var(--uv-veil) / .06); color:var(--uv-ink-soft);
  -webkit-tap-highlight-color:transparent;
}
.uv-tile img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; opacity:0; transition:opacity .2s ease; }
.uv-tile.loaded img{ opacity:1; }
.uv-tile::after{ content:""; position:absolute; inset:0; border-radius:inherit; pointer-events:none; transition:box-shadow .15s ease, background .15s ease; }
@media (hover:hover){ .uv-tile:hover::after{ background:rgb(var(--uv-veil) / .08); } }
.uv-tile.current::after{ box-shadow:inset 0 0 0 3px var(--uv-ink); }
.uv-tile:focus-visible{ outline:none; }
.uv-tile:focus-visible::after{ box-shadow:inset 0 0 0 3px var(--uv-ink), inset 0 0 0 5px var(--uv-bg); }
.uv-tile-play{
  position:absolute; left:6px; bottom:6px; width:24px; height:24px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:rgba(10,12,16,.55); color:#fff; pointer-events:none;
}
.uv-tile-play svg{ width:13px; height:13px; display:block; }
.uv-tile-name{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center; padding:8px; font-size:11.5px; line-height:1.3; text-align:center; overflow-wrap:anywhere; overflow:hidden; }
.uv-tile.loaded .uv-tile-name, .uv-tile[data-audio] .uv-tile-name{ display:none; }
/* Audio tiles (script: grep "uv grid audio"): drawn art like the audio
   card (--aud gradient, note badge, audioWaveBars waveform, extension
   label, 2-line name). .audio-current = player's track (stronger bars +
   progress, --audio-p), .audio-playing = sounding (bars dance). */
.uv-tile-audio{
  --aud:#B8560B;
  position:absolute; inset:0; display:flex; flex-direction:column; justify-content:flex-end; gap:2px;
  padding:10px 10px 11px; text-align:left;
  background:linear-gradient(140deg, color-mix(in srgb, var(--aud) 30%, var(--uv-bg)), color-mix(in srgb, var(--aud) 10%, var(--uv-bg)));
  color:color-mix(in srgb, var(--aud) 72%, var(--uv-ink));
}
.uv-tile-audio-note{
  position:absolute; top:8px; right:8px; width:24px; height:24px; border-radius:7px;
  display:flex; align-items:center; justify-content:center;
  background:color-mix(in srgb, var(--aud) 85%, #000); color:#fff;
  box-shadow:0 2px 6px rgba(0,0,0,.16);
}
.uv-tile-audio-note svg{ width:13px; height:13px; display:block; }
.uv-tile-wave{ position:absolute; left:14%; right:14%; top:20%; height:34%; display:flex; align-items:center; gap:2px; opacity:.42; transition:opacity .2s ease; }
.uv-tile-wave i{ flex:1; height:var(--h); border-radius:99px; background:currentColor; transform-origin:center; }
.uv-tile-audio-ext{ font-size:9.5px; font-weight:700; letter-spacing:.07em; text-transform:uppercase; line-height:1.2; }
.uv-tile-audio-name{
  font-size:12px; font-weight:600; line-height:1.28; color:var(--uv-ink);
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; overflow-wrap:anywhere;
}
.uv-tile-audio-progress{ position:absolute; left:0; right:0; bottom:0; height:3px; }
.uv-tile-audio-progress::before{ content:""; position:absolute; inset:0; transform-origin:left; transform:scaleX(var(--audio-p, 0)); background:color-mix(in srgb, var(--aud) 80%, var(--uv-ink)); }
.uv-tile.audio-current .uv-tile-wave{ opacity:.75; }
.uv-tile.audio-playing .uv-tile-wave{ opacity:.9; }
.uv-tile.audio-playing .uv-tile-wave i{ animation:card-wave .9s ease-in-out infinite alternate; animation-delay:var(--d); }
@media (prefers-reduced-motion: reduce){ .uv-tile.audio-playing .uv-tile-wave i{ animation:none; } }
@media (max-width: 760px){
  .uv-grid{ grid-template-columns:repeat(3, 1fr); gap:2px; padding:2px 0 calc(12px + env(safe-area-inset-bottom, 0px)); }
  .uv-tile{ border-radius:0; }
}
/* Manga mode (uv-manga-btn, shown only in grid mode; script: grep "uv
   manga"): the list's IMAGES in one vertical scroll, full width up to
   --uv-manga-max, no gaps (webtoon strips join). The grid stays built
   underneath. Each .uv-page reserves 2:3 until its picture gives the real
   ratio (inline, by the script, which also steadies the scroll when a page
   ABOVE changes height; overflow-anchor:none so the browser doesn't
   correct twice). */
.uv.uv-manga-mode .uv-grid{ display:none; }
.uv-manga{
  --uv-manga-max:900px;
  flex:1; min-height:0; overflow-y:auto; overscroll-behavior:contain; overflow-anchor:none;
  touch-action:pan-y; -webkit-overflow-scrolling:touch; outline:none;
  padding:0 0 env(safe-area-inset-bottom, 0px);
  animation:uv-fade-in .16s ease;
}
.uv-manga[hidden]{ display:none; }
@media (prefers-reduced-motion: reduce){ .uv-manga{ animation:none; } }
.uv-page{
  position:relative; width:100%; max-width:var(--uv-manga-max); margin:0 auto;
  aspect-ratio:2 / 3; background:rgb(var(--uv-veil) / .04);
}
.uv-page img{ position:absolute; inset:0; width:100%; height:100%; display:block; opacity:0; transition:opacity .2s ease; -webkit-user-drag:none; }
.uv-page.loaded{ background:none; }
.uv-page.loaded img{ opacity:1; }
.uv-page-msg{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center; padding:16px; text-align:center; font-size:12.5px; color:var(--uv-ink-soft); word-break:break-all; }
.uv-page.loaded .uv-page-msg{ display:none; }
.uv-msg button{ pointer-events:auto; font:inherit; font-weight:600; color:var(--uv-ink); background:var(--uv-btn); border:none; border-radius:99px; padding:8px 16px; cursor:pointer; }
.uv-spinner{ width:26px; height:26px; border-radius:50%; border:3px solid rgb(var(--uv-veil) / .18); border-top-color:var(--uv-ink); animation:thumb-spin .8s linear infinite; }
@media (prefers-reduced-motion: reduce){ .uv-spinner{ animation:none; } }

/* ---------- mobile (placed last on purpose: with tied specificity,
   source order decides, so overrides must come after what they beat) ---------- */
@media (max-width: 760px){
  .shell{ grid-template-columns:1fr; grid-template-areas:"topbar" "main"; }

  .menu-btn{
    display:flex; align-items:center; justify-content:center;
    width:36px; height:36px; flex:none;
    border:1px solid var(--line-strong); border-radius:var(--radius-sm);
    background:var(--surface); color:var(--ink); cursor:pointer;
  }

  .topbar{ gap:10px; padding:0 12px; }
  .search{ display:none; }
  .search-peek{ display:flex; }
  .topbar-actions{ gap:6px; }
  .btn-primary .btn-label{ display:none; }
  .btn-primary{ padding:9px; }
  #pasteCancelBtn .btn-label{ display:none; }
  #pasteCancelBtn{ padding:9px; }
  .section-head .sort-word{ display:none; }
  /* .select-cancel-fab needs no override: already a fixed circle at all
     widths; only body.select-mode-active shows it, and desktop uses the
     bar's Cancel. */

  .rail{
    position:fixed; top:0; left:0; bottom:0; z-index:50;
    height:auto; align-self:auto;
    width:250px; max-width:80vw;
    transform:translateX(-100%);
    transition:transform .2s ease;
  }
  .rail.open{ transform:translateX(0); box-shadow:16px 0 40px rgba(18,21,28,.18); }

  .rail-scrim{
    display:none; position:fixed; inset:0; background:rgba(15,17,21,.35); z-index:40;
  }
  .rail-scrim.open{ display:block; }
  /* Drawer open: the page behind must not scroll. html.rail-open from
     setRailOpen(); scoped to this block so a window widened to desktop with
     the drawer open isn't left locked. The scrim opts out of touch panning;
     the drawer's own scroll never chains to the page. */
  html.rail-open, html.rail-open body{ overflow:hidden; }
  .rail-scrim{ touch-action:none; }
  .rail{ overscroll-behavior:contain; }

  .account-pop{
    position:fixed; top:58px; right:12px; left:12px; width:auto;
  }

  /* Phone: icon + chevron only, 36px tap target; negative margins keep the row at 24px. */
  .dz-toggle{ height:36px; margin:-6px 0; padding:0 12px; }
  html.dz-hidden .dz-toggle .dz-toggle-label{ display:none; }

  .dropzone{ flex-wrap:wrap; }
  /* basis 0: text shrinks beside the icon instead of wrapping below it
     (<~326px). .dz-actions (width:100%) gets its own row. */
  .dropzone .dz-text{ flex:1 1 0; min-width:0; }
  /* Column: side by side would push "Upload folder" off narrow screens. */
  .dropzone .dz-actions{ margin-left:0; width:100%; order:3; flex-direction:column; }
  .dropzone .dz-actions .btn{ width:100%; justify-content:center; }
  /* Android/iOS: no buttons, the whole zone is one tap target: needn't be tall. */
  .dropzone.dz-no-folder{ padding:14px 16px; gap:12px; margin-bottom:18px; }
  .dropzone.dz-no-folder .dz-icon{ width:38px; height:38px; }

  .intro-card{ padding:12px 14px; gap:10px; }
  .intro-card .intro-icon{ width:30px; height:30px; }

  .main{ padding:18px 14px 32px; }

  .toast-host{ top:66px; left:14px; right:14px; width:auto; max-width:none; }

  .site-footer{ gap:6px 16px; font-size:12px; }
}

/* narrowest phones (≥320px), on top of the block above */
@media (max-width: 360px){
  .topbar{ gap:8px; padding:0 10px; }
  .brand{ font-size:18px; gap:6px; }
  .brand-mark{ width:24px; height:24px; }
  .account-trigger .chev{ display:none; }
  .account-trigger{ padding:3px; gap:0; }
  .grid{ grid-template-columns:minmax(0,1fr); } /* plain 1fr = minmax(auto,1fr): a nowrap filename would stretch the track */
  .section-head{ flex-wrap:wrap; }
  #newBtn{ padding:9px 11px; gap:4px; flex:none; }
  #newBtn .nf-icon{ display:flex; }
  #newBtn .nf-label{ display:none; }
  #topSelectBtn:not(.active):not(.all-selected){ width:34px; padding:0; }
  #topSelectBtn .sel-label, #topSelectBtn:not(.active):not(.all-selected) .sel-label{ display:none; }
}
