:root {
  --accent-contrast: #0d1218;
  --chrome:        linear-gradient(180deg, #181d25 0%, #151a21 100%);
  --bg:            #14181f;
  --bg-elevated:   #1c2129;
  --bg-hover:      #232935;
  --surface:       #1e242d;
  --surface-2:     #262e39;
  --border:        #2c3542;
  --border-strong: #3a4453;
  --text:          #e6ecf3;
  --text-muted:    #8e9bad;
  --text-dim:      #6b7789;
  --accent:        #4fb0e6;
  --accent-bright: #7bc9f2;
  --accent-soft:   rgba(79, 176, 230, 0.14);
  --accent-glow:   rgba(79, 176, 230, 0.25);
  --danger:        #e07d8f;
  --danger-soft:   rgba(224, 125, 143, 0.12);
  --success:       #6ec99a;
  --warning:       #f0c674;
  --purple:        #b48ee6;
  --radius:        12px;
  --radius-sm:     7px;
  --shadow:        0 2px 8px rgba(0, 0, 0, 0.35);
  --shadow-lg:     0 12px 40px rgba(0, 0, 0, 0.55);
  --sidebar-w:     240px;
}

* { box-sizing: border-box; }

/* Author styles like .opt{display:flex} must not defeat the hidden attribute */
[hidden] { display: none !important; }

html { color-scheme: dark; }
html[data-theme="light"] { color-scheme: light; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-bright); }

h1, h2, h3 { margin: 0 0 0.5rem; font-weight: 650; letter-spacing: -0.015em; }
h1 { font-size: 1.55rem; }
h2 { font-size: 1.1rem; color: var(--text); }

.muted { color: var(--text-muted); }
code {
  background: var(--surface-2);
  padding: 0.05rem 0.35rem;
  border-radius: 4px;
  font-size: 0.85em;
  color: var(--accent-bright);
  word-break: break-all;
}

/* ================= Shell & sidebar ================= */

.shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  min-height: 100vh;
  min-height: 100dvh;
}

.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--chrome);
  border-right: 1px solid var(--border);
  padding: 1.1rem 0.85rem;
  gap: 0.75rem;
}

.sidebar-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 0.35rem;
}

.sidebar-toggle { display: none; }

.brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 750;
  color: var(--text);
  letter-spacing: -0.02em;
  font-size: 1.02rem;
}
.brand:hover { color: var(--text); }

.brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 9px;
  background: linear-gradient(135deg, var(--accent) 0%, #3a8fc0 100%);
  color: var(--accent-contrast);
  box-shadow: 0 2px 10px var(--accent-glow);
  flex-shrink: 0;
}

.side-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  overflow-y: auto;
  padding-top: 0.5rem;
}

.nav-section {
  font-size: 0.7rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--text-dim);
  padding: 0.9rem 0.65rem 0.35rem;
}
.nav-section:first-child { padding-top: 0.2rem; }

.side-link {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.52rem 0.65rem;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-weight: 500;
  font-size: 0.92rem;
  transition: color 0.12s, background 0.12s;
}
.side-link svg { flex-shrink: 0; opacity: 0.85; }
.side-link:hover { color: var(--text); background: var(--bg-hover); }
.side-link.side-active {
  color: var(--accent-bright);
  background: var(--accent-soft);
}
.side-link.side-active svg { opacity: 1; }

.sidebar-foot {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  border-top: 1px solid var(--border);
  padding-top: 0.8rem;
}

.side-user {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.45rem 0.55rem;
  border-radius: var(--radius-sm);
  color: var(--text);
  font-weight: 500;
  font-size: 0.9rem;
  min-width: 0;
}
.side-user:hover { background: var(--bg-hover); color: var(--text); }
.side-user.side-active { background: var(--accent-soft); }
.side-user-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent) 0%, #3a8fc0 100%);
  color: var(--accent-contrast);
  font-weight: 750;
  font-size: 0.82rem;
  text-transform: uppercase;
  flex-shrink: 0;
}

.foot-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.logout-form { width: 100%; }
.logout-btn { gap: 0.4rem; width: 100%; }

.switch-row { display: flex; align-items: center; gap: 0.5rem; }

/* Language switch (segmented) */
.lang-switch {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
  background: var(--bg);
}
.lang-opt {
  padding: 0.22rem 0.65rem;
  font-size: 0.76rem;
  font-weight: 650;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.lang-opt:hover { color: var(--text); }
.lang-opt.lang-active {
  background: var(--accent);
  color: var(--accent-contrast);
}
.lang-opt.lang-active:hover { color: var(--accent-contrast); }
.lang-opt-icon { display: inline-flex; align-items: center; padding: 0.22rem 0.5rem; }

/* ================= Main column ================= */

.main {
  min-width: 0;
  width: 100%;
  max-width: 960px;
  margin: 0 auto;
  padding: 2rem 2rem 4rem;
}

/* ---- Disk usage bar ---- */
.disk-bar {
  margin: 0 0 1.75rem;
  padding: 0.8rem 1rem 0.85rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.disk-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.disk-title {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.76rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-dim);
}
.disk-title svg { color: var(--accent); }
.disk-figures {
  font-size: 0.88rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.disk-figures strong { color: var(--text); font-weight: 650; }
.disk-pct {
  margin-left: 0.5rem;
  padding: 0.05rem 0.5rem;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-bright);
  font-weight: 650;
  font-size: 0.8rem;
}
.disk-track {
  height: 8px;
  background: var(--bg);
  border-radius: 999px;
  overflow: hidden;
}
.disk-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-bright) 100%);
  transition: width 0.4s ease;
}
.disk-free {
  margin: 0.45rem 0 0;
  font-size: 0.78rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.disk-free .chip { vertical-align: middle; margin-left: 0.2rem; }
.disk-warn .disk-fill { background: linear-gradient(90deg, #d9a441 0%, var(--warning) 100%); }
.disk-warn .disk-pct { background: rgba(240, 198, 116, 0.16); color: var(--warning); }
.disk-critical .disk-fill { background: linear-gradient(90deg, #c2566c 0%, var(--danger) 100%); }
.disk-critical .disk-pct { background: var(--danger-soft); color: var(--danger); }
.disk-critical { border-color: rgba(224, 125, 143, 0.35); }

.page-head { margin-bottom: 1.5rem; }
.page-sub { margin: 0.15rem 0 0; color: var(--text-muted); font-size: 0.95rem; }

.badge {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-bright);
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  vertical-align: middle;
}
.badge-super {
  background: linear-gradient(135deg, #4fb0e6 0%, #7bc9f2 100%);
  color: #0d1218;
  box-shadow: 0 0 0 1px rgba(123, 201, 242, 0.3);
}

/* ================= Buttons ================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0.5rem 0.95rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.15s ease;
  text-decoration: none;
  line-height: 1;
  white-space: nowrap;
}
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.btn-primary { background: var(--accent); color: var(--accent-contrast); font-weight: 650; }
.btn-primary:hover { background: var(--accent-bright); color: var(--accent-contrast); }

.btn-ghost { color: var(--text-muted); border-color: var(--border); }
.btn-ghost:hover { color: var(--text); border-color: var(--border-strong); background: var(--bg-hover); }

.btn-danger { color: var(--danger); background: var(--danger-soft); }
.btn-danger:hover { color: #fff; background: var(--danger); }

.btn-oidc { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.btn-oidc:hover { border-color: var(--accent); background: var(--bg-hover); color: var(--text); }

.btn-block { width: 100%; padding: 0.7rem 1rem; }
.btn-stacked { margin-top: 0.5rem; }
.btn-sm { padding: 0.38rem 0.7rem; font-size: 0.85rem; }
.btn[disabled], .btn:disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }

/* ================= Cards ================= */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.4rem 1.6rem;
}
.card-narrow { max-width: 560px; }

/* ================= Uploader ================= */

.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  padding: 3.25rem 1.5rem;
  border: 2px dashed var(--border-strong);
  border-radius: var(--radius);
  background:
    radial-gradient(ellipse at 50% -20%, var(--accent-soft) 0%, transparent 55%),
    linear-gradient(180deg, var(--surface) 0%, var(--bg-elevated) 100%);
  cursor: pointer;
  text-align: center;
  transition: all 0.2s ease;
  color: var(--text-muted);
}
.dropzone:hover, .dropzone-hover {
  border-color: var(--accent);
  color: var(--text);
}
.dropzone-hover {
  transform: scale(1.004);
  box-shadow: 0 0 0 5px var(--accent-soft);
}

.dropzone-icon { color: var(--accent); margin-bottom: 0.25rem; }
.dropzone:hover .dropzone-icon { color: var(--accent-bright); }

.dropzone-text { font-size: 1.1rem; color: var(--text); font-weight: 550; }
.dropzone-text em { color: var(--accent-bright); font-style: normal; }
.dropzone-hint { font-size: 0.84rem; color: var(--text-dim); }

.dropzone input[type=file] {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); border: 0;
}

/* The second way into the dropzone: paste. Sits under it rather than inside,
   because inside a <label for=file-input> every click also opens the picker. */
.dropzone-aux {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
  margin-top: 0.7rem;
}
.dropzone-aux-hint { font-size: 0.82rem; color: var(--text-dim); }

/* ---- the two upload steps ---- */

.step {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 1.1rem 1.15rem 1.15rem;
  margin-bottom: 0.9rem;
  transition: opacity 0.15s ease, border-color 0.15s ease;
}
.step-head {
  display: flex;
  align-items: flex-start;
  gap: 0.8rem;
  margin-bottom: 0.6rem;
}
.step-titles { flex: 1; min-width: 0; }
.step-title { font-size: 1rem; margin: 0; }
.step-sub { margin: 0.15rem 0 0; font-size: 0.85rem; color: var(--text-dim); }
.step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-bright);
  font-size: 0.82rem;
  font-weight: 700;
}
.step-chip { flex-shrink: 0; }
.step-actions {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  flex-wrap: wrap;
  margin-top: 1rem;
}
.step-hint { font-size: 0.82rem; color: var(--text-dim); }

/* Locked: visibly present, plainly not ready. The dropzone inside is inert
   because its input is disabled, not because a style says so. */
.step-locked { opacity: 0.55; border-style: dashed; }
.step-locked .step-num { background: var(--surface-2); color: var(--text-dim); }
.step-locked .dropzone {
  cursor: not-allowed;
  border-color: var(--border);
  background: var(--surface-2);
}
.step-locked .dropzone:hover { border-color: var(--border); color: var(--text-muted); }
.step-locked .dropzone-icon { color: var(--text-dim); }
.step-locked .dropzone-text { color: var(--text-muted); }
.step-locked .dropzone-aux { opacity: 0.75; }
/* Settled step 1: the fields stay readable, but nothing invites a change. */
.step-done { opacity: 0.75; }
.step-done .step-num { background: var(--success-soft, var(--surface-2)); color: var(--success); }
.step-collapsed .step-body { display: none; }

/* ---- Single-Use preset ---- */

.quick-opts {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin: 1rem 0 0;
}
.quick-opts-hint {
  font-size: 0.82rem;
  color: var(--text-dim);
}

/* Styled off aria-pressed rather than a class, so the pressed look and the
   state screen readers announce cannot drift apart. */
.btn-toggle[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-bright);
}
.btn-toggle[aria-pressed="true"]:hover {
  background: var(--accent-soft);
  border-color: var(--accent-bright);
  color: var(--accent-bright);
}

.options {
  margin: 1rem 0 0;
  padding: 1rem 1.15rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.9rem;
}
.options legend {
  padding: 0 0.4rem;
  font-size: 0.76rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 600;
}
.opt { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.85rem; color: var(--text-muted); }
.opt input, .opt select {
  padding: 0.5rem 0.65rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: 0.92rem;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.opt input:focus, .opt select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* ================= Upload queue ================= */

.session-uploads { margin-top: 2rem; }

.files-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.85rem;
}
.view-all { font-size: 0.88rem; font-weight: 500; }

.queue {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.queue-item {
  padding: 0.8rem 1.05rem 0.9rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.queue-item:last-child { border-bottom: none; }

.queue-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  font-size: 0.9rem;
}
.queue-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
  font-weight: 550;
  /* min-width:0 lets the ellipsis engage once the action buttons claim room */
  flex: 1;
  min-width: 0;
}
.queue-status {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
  font-size: 0.85rem;
}

.queue-bar {
  height: 6px;
  background: var(--bg);
  border-radius: 999px;
  overflow: hidden;
}
.queue-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-bright) 100%);
  transition: width 0.15s ease;
}
.queue-done .queue-fill { background: var(--success); }
.queue-done .queue-status { color: var(--success); }
.queue-error .queue-fill { background: var(--danger); width: 100%; }
.queue-error .queue-status { color: var(--danger); }
.queue-cancelled .queue-fill { background: var(--text-muted); width: 0%; }
.queue-cancelled .queue-status { color: var(--text-muted); }

.queue-actions {
  display: flex;
  gap: 0.35rem;
  flex-shrink: 0;
}
.queue-actions .btn { padding: 0.2rem 0.6rem; font-size: 0.78rem; }

/* The failure reason is part of the row, not a tooltip: it survives pointer
   movement, focus changes and scrolling, and stays until the row is retried. */
.queue-reason {
  margin: 0;
  padding-left: 0.6rem;
  border-left: 2px solid var(--danger);
  color: var(--text-muted);
  font-size: 0.82rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.queue-cancelled .queue-reason { border-left-color: var(--border-strong); }

/* ---- batch share panel (upload page) ---- */

.batch-share {
  padding: 0.9rem 1.05rem 1rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.batch-share-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.batch-share-label {
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--text);
}
.batch-share-note {
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--text-muted);
}
.batch-share #batch-share-new { align-self: flex-start; }

.options-locked { opacity: 0.55; }
.options-locked input:disabled,
.options-locked select:disabled { cursor: not-allowed; }

/* ---- batch landing list ---- */

.batch-list {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.batch-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.7rem 0.85rem;
  border-bottom: 1px solid var(--border);
  /* the preview panel wraps onto its own full-width line below the row */
  flex-wrap: wrap;
}
.batch-row-actions {
  display: flex;
  gap: 0.35rem;
  flex-shrink: 0;
}
/* Same story as the gallery rules below: .dl-preview's margin shorthand sits
   later in the file at equal specificity, so a bare .batch-preview margin-top
   never applied and the inline preview sat flush against its row. */
.dl-preview.batch-preview {
  flex-basis: 100%;
  margin: 0.7rem 0 0;
}
.batch-row:last-child { border-bottom: none; }
.batch-row-meta {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  flex: 1;
  min-width: 0;
}
.batch-row-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
  font-weight: 550;
  font-size: 0.9rem;
}
.batch-row-sub {
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A member the sealed roster does not vouch for. It is still downloadable and
   its own bytes are still authenticated — only its membership of this link is
   unproven — so it is flagged, not hidden. */
.batch-row-warn {
  align-self: flex-start;
  font-size: 0.72rem;
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius-sm);
  background: rgba(224, 178, 110, 0.14);
  color: var(--warn, #e0b26e);
  border: 1px solid rgba(224, 178, 110, 0.3);
}
.batch-row .btn { flex-shrink: 0; }

/* Each row carries its own download button, and that button IS its progress
   readout: the card's shared bar sits above the list, so once a preview is
   open it is scrolled out of sight exactly when a transfer is running. */
.row-download {
  position: relative;
  overflow: hidden;
  align-self: flex-start;
  margin-top: 0.4rem;
  min-width: 12rem;
  justify-content: flex-start;
}
.row-download-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0%;
  background: var(--accent-soft);
  border-right: 1px solid var(--accent);
  opacity: 0;
  transition: width 0.15s ease;
  pointer-events: none;
}
.row-download.is-busy .row-download-fill { opacity: 1; }
.row-download.is-busy { color: var(--accent-bright); }
.row-download-label {
  position: relative;
  font-variant-numeric: tabular-nums;
}
/* A finished transfer keeps the bar full rather than snapping back to empty,
   so a download that ran while the row was off-screen still says so. */
.row-download.is-done .row-download-fill {
  opacity: 1;
  background: var(--success-soft, rgba(110, 201, 154, 0.14));
  border-right-color: var(--success);
}
.row-download.is-done { color: var(--success); }
/* ---- "Preview all" gallery ---- */
/* A <dialog> in the top layer, so no z-index has to be negotiated with the
   sticky sidebar or the toast. The dialog element is only the frame: all
   padding lives on .gallery-box, which is what lets a click on the dialog
   itself mean "the backdrop was clicked". */
/* Full-bleed: the whole viewport, so the stage gets every pixel there is. */
.gallery {
  position: fixed;
  inset: 0;
  width: 100vw;
  max-width: 100vw;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--text);
  overflow: hidden;
}
.gallery::backdrop { background: rgba(8, 11, 15, 0.78); }

.gallery-box {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  height: 100%;
  padding: 0.9rem 1.1rem 1.1rem;
  /* Phones put their own bar over the bottom of the viewport; without this the
     note and the thumbnail strip end up underneath it. */
  padding-bottom: calc(1.1rem + env(safe-area-inset-bottom, 0px));
  background: var(--surface);
  border: none;
  border-radius: 0;
  /* Nothing escapes the column: the stage and the strip give up height first
     (both can shrink to nothing and scroll internally), so the rows that carry
     text are never pushed off the bottom or drawn over. */
  overflow: hidden;
}
.gallery-head,
.gallery-caption,
.gallery-progress,
.gallery-error,
.gallery-note { flex: 0 0 auto; }

.gallery-head {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.gallery-title {
  margin: 0;
  margin-right: auto;
  font-size: 1rem;
}
.gallery-close { flex-shrink: 0; padding: 0.4rem; }

/* The stage stretches its children rather than centring them, so the preview
   box inherits the full height by being a stretched flex item instead of by a
   percentage resolved against an auto-height parent. The arrow buttons opt
   back out with align-self. */
.gallery-stage {
  display: flex;
  align-items: stretch;
  gap: 0.6rem;
  /* Takes every row the head, caption and strip do not claim. */
  flex: 1 1 auto;
  min-height: 0;
}
.gallery-view {
  flex: 1;
  min-width: 0;
  min-height: 0;
  height: 100%;
  display: flex;
  align-items: stretch;
  justify-content: center;
}
.gallery-nav {
  flex-shrink: 0;
  align-self: center;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-muted);
  cursor: pointer;
  transition: all 0.15s ease;
}
.gallery-nav:hover {
  color: var(--text);
  border-color: var(--border-strong);
  background: var(--bg-hover);
}
.gallery-nav:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The stage reuses .dl-preview so the sniffing rules and the media styling
   stay in one place; only the sizing differs. */
/* Every rule here is written .dl-preview.gallery-preview rather than just
   .gallery-preview. The generic .dl-preview rules live further down the file
   and carry the same one-class specificity, so on source order alone they win
   — which is what capped a full-screen gallery image at 420px and left it
   marooned in the middle of the stage. Two classes settle it wherever the
   rules happen to sit, including inside the narrow-screen media query. */
.dl-preview.gallery-preview {
  margin: 0;
  flex: 1;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
}
/* Percentages, not viewport units: the stage is what is left after the strip
   and the caption, and the media has to fit THAT, not the screen.

   Sized rather than merely capped. max-width/max-height only ever scale an
   image DOWN, so on a full-screen stage anything smaller than the viewport
   kept its natural size and sat in the middle of the box with a wide rim of
   background around it. width/height 100% plus object-fit:contain scales in
   both directions, so the picture grows to fill the stage and keeps its
   aspect ratio. */
.dl-preview.gallery-preview img,
.dl-preview.gallery-preview video {
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: contain;
}
.dl-preview.gallery-preview iframe { height: 100%; }
/* Text has no aspect ratio to preserve — it just takes the whole panel. */
.dl-preview.gallery-preview .e2e-text { height: 100%; max-height: 100%; }

.gallery-caption {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.88rem;
}
.gallery-name {
  font-weight: 550;
  color: var(--text);
  margin-right: auto;
  overflow-wrap: anywhere;
}
.gallery-counter {
  flex-shrink: 0;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.gallery-warn { flex-shrink: 0; align-self: center; }
.gallery-progress { padding: 0; border-bottom: none; }
.gallery-error { margin: 0; }
.gallery-note { margin: 0; font-size: 0.78rem; }

/* One row that scrolls sideways, not a wrapping grid.
   A grid wraps to as many rows as it needs and then has to be capped, and any
   cap that is not an exact multiple of a row height slices the bottom row
   through the middle — which is what put half-tiles directly against the note
   underneath and read as the two overlapping. A single row has one height, is
   never sliced, and leaves the stage the rest of the screen. */
.gallery-strip {
  list-style: none;
  margin: 0;
  padding: 0.15rem 0 0.2rem;
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  overflow-y: hidden;
  /* Never shrinks: its height is exactly one tile, and giving that up is what
     produced the sliced row. The stage absorbs every bit of the squeeze. */
  flex: 0 0 auto;
  scrollbar-width: thin;
  overscroll-behavior-x: contain;
}
.gallery-strip > li { flex: 0 0 88px; }
.gallery-tile {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: 0.4rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font: inherit;
  font-size: 0.7rem;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.gallery-tile:hover { border-color: var(--border-strong); color: var(--text); }
.gallery-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.gallery-tile[aria-current="true"] {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--text);
}
.gallery-thumb {
  width: 100%;
  aspect-ratio: 4 / 3;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 5px;
  background: var(--surface-2);
  color: var(--text-dim);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.07em;
}
/* A tile whose file is being fetched in the background. Deliberately quiet:
   the prefetch is not something anyone is waiting on. */
.gallery-tile.is-loading .gallery-thumb {
  animation: gallery-thumb-pulse 1.2s ease-in-out infinite;
}
@keyframes gallery-thumb-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
  .gallery-tile.is-loading .gallery-thumb { animation: none; opacity: 0.6; }
}
/* The tile keeps the icon's per-kind colour but not its 40px chip: the thumb
   itself is already the box. */
.gallery-thumb .ficon {
  width: 100%;
  height: 100%;
  border-radius: 0;
  background: transparent;
}
.gallery-thumb .ficon-svg { width: 26px; height: 26px; }
.gallery-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.gallery-tile-name {
  width: 100%;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.batch-empty {
  padding: 1rem;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.88rem;
}

.queue-link {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.queue-link input {
  flex: 1;
  min-width: 0;
  padding: 0.42rem 0.6rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--accent-bright);
  font: inherit;
  font-size: 0.85rem;
}
.queue-link input:focus { outline: none; border-color: var(--accent); }

/* ================= Stats & search ================= */

.stats-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.85rem;
  margin-bottom: 1.25rem;
}
.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
}
.stat-value {
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.stat-label {
  font-size: 0.78rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}

.search-row {
  position: relative;
  margin-bottom: 1rem;
}
.search-icon {
  position: absolute;
  left: 0.85rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-dim);
  pointer-events: none;
}
.search-input {
  width: 100%;
  padding: 0.6rem 0.9rem 0.6rem 2.5rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: 0.92rem;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.search-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.count {
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  padding: 0.05rem 0.55rem;
  background: var(--surface);
  border-radius: 999px;
  border: 1px solid var(--border);
  font-weight: 500;
  vertical-align: middle;
}

/* ================= File list ================= */

.file-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.file-item {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.8rem 1.05rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color 0.15s ease;
}
.file-item:hover { border-color: var(--border-strong); }

.ficon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--text-muted);
  flex-shrink: 0;
}
/* The <symbol> definitions the .ficon <use> elements point at. Never painted
   itself; symbols still resolve out of a display:none container. */
.icon-sprite { display: none; }
.ficon-image   { color: var(--success); background: rgba(110, 201, 154, 0.12); }
.ficon-video   { color: var(--accent-bright); background: var(--accent-soft); }
.ficon-audio   { color: var(--purple); background: rgba(180, 142, 230, 0.12); }
.ficon-pdf     { color: var(--danger); background: var(--danger-soft); }
.ficon-text    { color: var(--warning); background: rgba(240, 198, 116, 0.12); }
.ficon-doc     { color: var(--accent-bright); background: var(--accent-soft); }
.ficon-archive { color: var(--text-muted); background: var(--surface-2); }

.file-main { min-width: 0; flex: 1; }

.file-name-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 0.2rem;
}
.file-name {
  font-weight: 600;
  color: var(--text);
  font-size: 0.97rem;
  word-break: break-all;
}
.file-name:hover { color: var(--accent-bright); }
/* A name this page cannot read: the file's own name is sealed under the key in
   its share link. Set apart from a real one on purpose. */
.file-name-sealed {
  font-style: italic;
  font-weight: 500;
  color: var(--text-dim);
}
/* A name only this browser remembers, from when it did the uploading. Nothing
   has authenticated it, so it is marked as the second-hand thing it is. */
.file-name-remembered { border-bottom: 1px dotted var(--border-strong); }

.file-meta {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  color: var(--text-dim);
  font-size: 0.82rem;
  flex-wrap: wrap;
}
.file-meta .dot { color: var(--border-strong); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.28rem;
  padding: 0.1rem 0.55rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 550;
  color: var(--text-muted);
  letter-spacing: 0.01em;
}
.chip-lock { background: var(--accent-soft); border-color: transparent; color: var(--accent-bright); }
.chip-expiry { color: var(--warning); }
.chip-warn { color: var(--warning); background: rgba(240, 198, 116, 0.14); border-color: transparent; }

/* ---- multi-select ---- */

.bulk-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
  padding: 0.5rem 0.75rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.bulk-bar[hidden] { display: none; }
.bulk-check {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.86rem;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
}
.bulk-count {
  font-size: 0.84rem;
  font-variant-numeric: tabular-nums;
  /* pushes the delete button to the far edge */
  margin-right: auto;
}
/* The button stays visible but inert with an empty selection: a control that
   disappears is harder to find again than one that is plainly not ready. */
#bulk-delete-btn:disabled { opacity: 0.45; cursor: not-allowed; }

.file-select {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  /* a click target larger than the 16px box, without moving the row */
  padding: 0.35rem;
  margin: -0.35rem 0 -0.35rem -0.35rem;
  cursor: pointer;
}
.file-select input,
.bulk-check input {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  cursor: pointer;
}
.file-item:has(.file-select input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.file-actions {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
}

/* ---- batch groups ---- */

/* The toolbar keeps the search box growing and parks the fold controls at its
   trailing edge. */
.list-toolbar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1rem;
}
.list-toolbar .search-row { flex: 1; margin-bottom: 0; }
.group-controls { display: flex; gap: 0.4rem; flex-shrink: 0; }

/* The group is a card with a header; its members are plain rows tucked inside
   it, so the outer <li> carries no border of its own. */
.file-group { display: block; }
.group-head {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.8rem 1.05rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color 0.15s ease;
  cursor: pointer;
}
.group-head:hover { border-color: var(--border-strong); }
/* Collapsed, the header is the whole card; expanded, it grows into the list
   below it. */
.file-group:not(.group-collapsed) .group-head {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom-color: transparent;
}
.group-head:has(.group-select:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.group-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}
.group-toggle:hover { background: var(--bg-hover); color: var(--text); }
/* The caret belongs to the icon beside it, not to the checkbox before it. */
.group-head .group-toggle { margin-right: -0.35rem; }
.group-caret { transition: transform 0.15s ease; }
.group-collapsed .group-caret { transform: rotate(-90deg); }
.ficon-batch { color: var(--accent-bright); background: var(--accent-soft); }

/* The indent: members sit inside the group card, offset from its left edge so
   the nesting is visible without a second border around them. */
.file-sublist {
  list-style: none;
  margin: 0;
  padding: 0.5rem 0.5rem 0.5rem 2.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  border: 1px solid var(--border);
  border-top: none;
  border-bottom-left-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
}
.group-collapsed .file-sublist { display: none; }
/* A child row is quieter than a top-level one: the share's terms are stated
   once, in the header above it. */
.file-child {
  padding: 0.55rem 0.75rem;
  background: var(--surface);
}
.file-actions form { display: inline; }
.btn-copy.copied { color: var(--success); border-color: var(--success); }

.file-archived { opacity: 0.55; }
.file-archived .ficon { color: var(--text-dim); background: var(--surface-2); }
.file-archived .file-name { color: var(--text-muted); }
.chip-archived { color: var(--danger); background: var(--danger-soft); border-color: transparent; }
.chip-keyed { color: var(--success); background: rgba(110, 201, 154, 0.12); border-color: transparent; }
.chip-batch { color: var(--text-muted); background: var(--bg-hover); border-color: transparent; }

.btn-disabled { opacity: 0.4; pointer-events: none; }

/* ---- End-to-end encryption ---- */
.e2e-badge {
  color: var(--success);
  font-weight: 650;
  letter-spacing: 0.06em;
}
.e2e-note {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0.85rem 0 0;
  font-size: 0.83rem;
  color: var(--text-dim);
}
.e2e-note svg { color: var(--success); flex-shrink: 0; }
.e2e-progress { border: 1px solid var(--border); border-radius: var(--radius-sm); margin-bottom: 1rem; }
.e2e-text {
  margin: 0;
  padding: 1rem;
  max-height: 300px;
  overflow: auto;
  width: 100%;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--text);
  white-space: pre-wrap;
  word-break: break-word;
}
#e2e-lock { max-width: 340px; margin: 0 auto 1rem; }

/* ---- QR modal ---- */
.qr-modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(8, 11, 15, 0.7);
  padding: 1.5rem;
}
.qr-box {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 1.5rem;
  text-align: center;
  max-width: 360px;
  width: 100%;
}
.qr-box img {
  width: 100%;
  max-width: 280px;
  border-radius: var(--radius-sm);
  background: #fff;
  padding: 10px;
  box-sizing: border-box;
}
.qr-box .qr-title {
  margin: 0 0 0.8rem;
  font-weight: 600;
  color: var(--text);
}
.qr-box .qr-url {
  margin: 0.8rem 0 0;
  font-size: 0.78rem;
  color: var(--text-dim);
  word-break: break-all;
}

.empty {
  padding: 2.75rem 1rem;
  text-align: center;
  color: var(--text-dim);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}

/* ================= Bare pages (login / download) ================= */

body.bare {
  min-height: 100vh;
  /* dvh follows the mobile URL bar, so the footer does not sit below the fold */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.bare-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--border);
  background: var(--chrome);
}

.bare-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 3rem 1.25rem 2rem;
}

.bare-foot {
  text-align: center;
  color: var(--text-dim);
  font-size: 0.8rem;
  padding: 1.25rem;
}

.dl-card {
  width: 100%;
  max-width: 420px;
  padding: 2.25rem 2rem 2rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  text-align: center;
}
.dl-card-wide { max-width: 640px; text-align: left; }

.dl-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 76px;
  height: 76px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-bright);
  margin-bottom: 1.1rem;
}
.dl-icon-muted { background: var(--surface-2); color: var(--text-dim); }

.dl-card h1 { font-size: 1.3rem; }
.dl-sub { color: var(--text-muted); font-size: 0.92rem; margin: 0.25rem 0 1.25rem; }
.dl-file-line { margin: 0.75rem 0; word-break: break-all; }

.dl-kicker {
  margin: 0 0 1.25rem;
  font-size: 0.78rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--accent-bright);
}

.dl-file-head {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: 1.25rem;
}
.dl-file-head .ficon { width: 52px; height: 52px; border-radius: 13px; }
.dl-file-head .ficon svg { width: 26px; height: 26px; }
.dl-file-title { min-width: 0; }
/* overflow-wrap:anywhere rather than word-break:break-all — a long file name or
   content type still breaks, but a short one is no longer chopped mid-word. */
.dl-file-title h1 {
  font-size: 1.25rem;
  margin: 0 0 0.1rem;
  overflow-wrap: anywhere;
}
.dl-file-title .muted {
  display: block;
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

.dl-preview {
  margin: 0 0 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.dl-preview img {
  max-width: 100%;
  max-height: 420px;
  display: block;
  object-fit: contain;
}
/* Click-to-enlarge: the click opens the .zoom overlay further down, so the
   preview image itself only needs to advertise that it is a target. */
.dl-preview img.preview-zoom { cursor: zoom-in; }
.dl-preview img.preview-zoom:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.dl-preview video { width: 100%; max-height: 420px; display: block; background: #000; }
.dl-preview audio { width: 100%; padding: 1.25rem 1rem; }
.dl-preview iframe {
  width: 100%;
  height: 420px;
  border: none;
  background: #fff;
}
.dl-preview-text iframe { background: var(--bg); height: 300px; }

/* ---- click-to-enlarge overlay ---- */
/* A second modal <dialog>, so it stacks in the top layer above the gallery —
   which is itself a modal dialog, and therefore above anything a z-index in
   the ordinary page could reach. */
.zoom {
  position: fixed;
  inset: 0;
  width: 100vw;
  max-width: 100vw;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
  overflow: hidden;
  /* The overlay handles the wheel and every touch gesture itself, so the
     browser must not treat a pinch or a drag on it as scrolling the page. */
  touch-action: none;
  user-select: none;
}
/* display goes on [open], never on .zoom itself. The UA stylesheet hides a
   closed dialog with `dialog:not([open]) { display: none }`, and an author
   `display` beats it on origin no matter the specificity — an unscoped one
   leaves the closed overlay on screen as a full-viewport pane that swallows
   every click and reappears the moment the dialog behind it goes away. */
.zoom[open] { display: flex; }
.zoom::backdrop { background: rgba(8, 11, 15, 0.92); }
/* As large as the screen allows and no larger: sized to the viewport with
   object-fit, so a small picture is fitted on opening rather than needing to
   be panned. From there app.js scales it up with a transform — which is why
   the element keeps the viewport's size at every zoom level, and only ever
   the transform changes. */
.zoom-img {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: contain;
  cursor: zoom-out;
  transform-origin: 50% 50%;
  will-change: transform;
  -webkit-user-drag: none;
}
/* Zoomed in, the picture is something to move rather than something to close,
   and the cursor should say so. */
.zoom.zoomed .zoom-img { cursor: grab; }
.zoom.zoomed.panning .zoom-img { cursor: grabbing; }

/* Names the gestures while the picture is fitted, and reads out the zoom level
   once it is not. Fixed rather than a flex item so it cannot squeeze the
   picture, and click-through so it is never in the way of a pan. */
.zoom-hint {
  position: fixed;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
}
.zoom-close {
  position: fixed;
  top: 0.9rem;
  right: 0.9rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  transition: all 0.15s ease;
}
.zoom-close:hover { color: var(--text); border-color: var(--border-strong); background: var(--bg-hover); }
.zoom-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.dl-nopreview {
  margin: 0 0 1.25rem;
  padding: 1.1rem;
  text-align: center;
  color: var(--text-dim);
  font-size: 0.88rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}
/* The same note, but standing in for a player that would not run inside the
   preview frame — which already draws the border this would otherwise repeat. */
.dl-preview > .dl-nopreview {
  margin: 0;
  padding: 1.6rem 1.25rem;
  border: none;
  max-width: 42ch;
  line-height: 1.5;
}

.dl-details {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 0.45rem 1rem;
  margin: 0 0 1.5rem;
  padding: 1rem 1.15rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
}
.dl-details dt { color: var(--text-muted); }
.dl-details dd {
  margin: 0;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  /* content types are long and unbreakable; without this the grid cell grows
     past the card and the whole page scrolls sideways on a phone */
  min-width: 0;
  overflow-wrap: anywhere;
}

.btn-download { font-size: 1rem; padding: 0.85rem 1rem; }

/* ================= Forms / auth ================= */

.auth-card { text-align: left; }
.auth-card h1, .auth-card .dl-sub { text-align: center; }
.auth-card .dl-icon { display: flex; margin: 0 auto 1.1rem; }

.auth-form { display: flex; flex-direction: column; gap: 0.85rem; }
.auth-form label { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.85rem; color: var(--text-muted); text-align: left; }
.auth-form input {
  padding: 0.65rem 0.8rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: 0.95rem;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.auth-form input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.auth-form button { margin-top: 0.4rem; }

.alert {
  padding: 0.6rem 0.85rem;
  margin: 0 0 1rem;
  background: var(--danger-soft);
  color: var(--danger);
  border-radius: var(--radius-sm);
  border: 1px solid rgba(224, 125, 143, 0.25);
  font-size: 0.88rem;
}
.alert-ok {
  background: rgba(110, 201, 154, 0.12);
  color: var(--success);
  border-color: rgba(110, 201, 154, 0.25);
}
/* Not an error: the download worked, but something about it could not be
   verified and the recipient should see that without the page looking broken. */
.alert-warn {
  background: rgba(224, 178, 110, 0.12);
  color: var(--warn, #e0b26e);
  border-color: rgba(224, 178, 110, 0.3);
}

.divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.25rem 0 1rem;
  color: var(--text-dim);
  font-size: 0.8rem;
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* ================= Account & admin ================= */

.account-info {
  display: grid;
  grid-template-columns: 150px 1fr;
  gap: 0.45rem 1rem;
  margin: 0 0 1.5rem;
  padding: 0.95rem 1.05rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  text-align: left;
}
.account-info dt { color: var(--text-muted); }
.account-info dd { margin: 0; color: var(--text); }

.section-title {
  font-size: 0.82rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 0 0 0.85rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
}

.denied-hint { font-size: 0.87rem; margin-bottom: 1.25rem; }

.admin-create { margin: 0.25rem 0 1.25rem; }
.admin-create > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
}
.admin-create > summary::-webkit-details-marker { display: none; }
.admin-create[open] > summary {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
}
.admin-create-form {
  margin-top: 1rem;
  padding: 1rem 1.15rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: grid;
  grid-template-columns: repeat(2, 1fr) auto auto;
  gap: 0.75rem;
  align-items: end;
}
.admin-create-form label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.82rem;
  color: var(--text-muted);
}
.admin-create-form input:not([type=checkbox]) {
  padding: 0.5rem 0.65rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
}
.admin-create-form input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.admin-check {
  flex-direction: row !important;
  align-items: center;
  gap: 0.4rem !important;
  color: var(--text);
}

/* Usage over quota, stacked: bytes on top, file count beneath. */
.quota-cell {
  font-variant-numeric: tabular-nums;
  line-height: 1.35;
}
.quota-cell > span { display: block; }
.quota-cell .muted { font-size: 0.82rem; }
.chip-quota {
  margin-left: 0.3rem;
  color: var(--accent-bright);
  background: var(--accent-soft);
  border-color: transparent;
}

.table-wrap { overflow-x: auto; }

.user-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  font-size: 0.9rem;
}
.user-table th, .user-table td {
  padding: 0.65rem 0.85rem;
  text-align: left;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.user-table tbody tr:last-child td { border-bottom: none; }
.user-table th {
  background: var(--bg-elevated);
  font-size: 0.76rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 650;
}
.user-table tbody tr:hover { background: var(--bg-hover); }
.cell-name { font-weight: 550; color: var(--text); }
.actions-col { text-align: right; }
.row-actions {
  text-align: right;
  white-space: nowrap;
  display: flex;
  gap: 0.35rem;
  justify-content: flex-end;
  align-items: center;
  flex-wrap: wrap;
}
.row-action { display: inline-block; }
.row-action[open] { background: var(--surface-2); border-radius: var(--radius-sm); }
.row-action > summary { list-style: none; cursor: pointer; display: inline-flex; }
.row-action > summary::-webkit-details-marker { display: none; }
.inline-form {
  display: inline-flex;
  gap: 0.35rem;
  align-items: center;
  padding: 0.4rem 0.5rem 0;
}
/* The row editors open as a narrow stacked panel rather than a wide line of
   inputs, at every width, for two reasons.
   The line was already too wide for the table: with the editor shut the table
   is exactly as wide as the page, and opening it grew the last cell by some
   340px, so the whole table — every column, every other row — slid sideways
   under a scrollbar just to type a number. A third field made that worse.
   And the fields need their captions. Side by side while empty the
   placeholders identify them, but a filled-in field shows no placeholder, and
   two of the three then read "1 GiB". */
.row-action .inline-form {
  display: grid;
  /* Wide enough for "20 GiB" and a readable caption, narrow enough that
     opening it barely widens the table; the last of that is dataset-dependent
     (a long email address decides the rest), so this is a compromise, not a
     number that makes the scrollbar go away for everyone. */
  width: 13rem;
  gap: 0.45rem;
  padding: 0.55rem 0.5rem 0.6rem;
}
.inline-field {
  display: grid;
  gap: 0.15rem;
}
.inline-field > span {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-muted);
}

.row-action .inline-form input {
  padding: 0.35rem 0.55rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
}

/* Below the width where the shell itself goes narrow (the sidebar collapses at
   the same breakpoint), the user table stops being a table and becomes one
   card per account. Seven columns do not fit a phone, and they do not fit a
   tablet held upright either: sideways-scrolled, the screen showed the name
   and the email and nothing else — the role, both limits, the date and every
   one of the row's buttons sat off to the right, behind a scroll nobody thinks
   to try on what looks like a finished page. */
@media (max-width: 900px) {
  /* Each cell keeps its column name in data-label (set in admin_users.html) and
     renders it above the value, so the heading row can go. */
  .table-wrap { overflow-x: visible; }
  .user-table {
    border: none;
    background: none;
    display: block;
    font-size: 0.95rem;
  }
  .user-table thead { display: none; }
  .user-table tbody { display: block; }
  .user-table tbody tr {
    display: block;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.9rem 1rem 1rem;
    margin-bottom: 0.75rem;
  }
  .user-table tbody tr:hover { background: var(--surface); }
  .user-table tbody tr:last-child { margin-bottom: 0; }
  .user-table td {
    display: block;
    border-bottom: none;
    padding: 0;
    margin-top: 0.7rem;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  .user-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 0.7rem;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-dim);
    margin-bottom: 0.15rem;
  }
  /* The name is the card's heading, so it takes no label and no top margin. */
  .user-table td.cell-name {
    margin-top: 0;
    font-size: 1.05rem;
    font-weight: 650;
  }

  /* Buttons: full width and stacked, rather than four targets crowded onto one
     line and then wrapped mid-word. */
  .user-table td.row-actions {
    display: grid;
    /* Both are needed: the cell is a flex row on desktop, and its
       justify-content: flex-end survives into the grid, where it packs a
       content-sized track against the right edge instead of filling the card.
       auto-fit gives a phone one button per line and a tablet a row of them,
       without a second breakpoint deciding where that happens. */
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    justify-content: stretch;
    gap: 0.4rem;
    white-space: normal;
    margin-top: 0.9rem;
    padding-top: 0.9rem;
    border-top: 1px solid var(--border);
  }
  .row-actions > form, .row-actions > .row-action, .row-actions > .btn { width: 100%; }
  .row-actions .btn {
    width: 100%;
    justify-content: center;
    /* Everything this layout covers is reachable by thumb — .btn-sm is 26px
       tall, and the rule that fixes that elsewhere only starts at 560px. */
    min-height: 38px;
  }
  .row-action > summary { display: flex; justify-content: center; }

  /* An open editor takes the whole row: on a tablet it would otherwise be
     squeezed into one of the button columns. */
  .row-action[open] {
    grid-column: 1 / -1;
    padding: 0.15rem 0.5rem 0.6rem;
  }
  .row-action .inline-form {
    width: 100%;
    padding: 0.55rem 0 0;
  }
  .row-action .inline-form input { width: 100%; }
}

/* ================= Settings ================= */

.settings-card { max-width: 720px; }
.settings-card + .settings-card { margin-top: 1.25rem; }
.settings-note {
  margin: 0 0 1.1rem;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--text-muted);
}
.settings-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.75rem;
}
.settings-head h2 { margin: 0; }

.settings-help {
  margin: 0.75rem 0 1.25rem;
  padding: 0.75rem 1rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
}
.settings-help > summary {
  cursor: pointer;
  font-weight: 500;
  color: var(--accent-bright);
  list-style: revert;
}
.settings-help[open] > summary { margin-bottom: 0.6rem; }
.settings-help ol {
  margin: 0.4rem 0 0.25rem;
  padding-left: 1.4rem;
  color: var(--text);
}
.settings-help li { margin: 0.35rem 0; }

.settings-form { display: flex; flex-direction: column; gap: 1rem; }
.settings-form label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.settings-form label > span:first-child { color: var(--text); font-weight: 550; }
.settings-form input:not([type=checkbox]) {
  padding: 0.55rem 0.7rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: 0.92rem;
}
.settings-form input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.settings-form .hint { font-style: normal; font-size: 0.8rem; color: var(--text-dim); }
.opt-check { flex-direction: row !important; align-items: center; gap: 0.5rem !important; }
.opt-check span { color: var(--text); font-weight: 550; }
.settings-actions {
  display: flex;
  justify-content: flex-end;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
}

/* ================= Toast ================= */

.toast {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%) translateY(2rem);
  padding: 0.65rem 1.1rem;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  font-size: 0.9rem;
  max-width: calc(100vw - 2rem);
  text-align: center;
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
  pointer-events: none;
  z-index: 40;
}
.toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}


/* ================= Light theme ================= */

:root[data-theme="light"] {
  --accent-contrast: #ffffff;
  --chrome:        linear-gradient(180deg, #ffffff 0%, #f4f6f9 100%);
  --bg:            #f3f5f8;
  --bg-elevated:   #eaeef3;
  --bg-hover:      #e3e9f0;
  --surface:       #ffffff;
  --surface-2:     #edf1f6;
  --border:        #d8dfe8;
  --border-strong: #b6c2d1;
  --text:          #1c242f;
  --text-muted:    #566375;
  --text-dim:      #7e8ca0;
  --accent:        #2b8fce;
  --accent-bright: #1b6fa6;
  --accent-soft:   rgba(43, 143, 206, 0.13);
  --accent-glow:   rgba(43, 143, 206, 0.22);
  --danger:        #c2455e;
  --danger-soft:   rgba(194, 69, 94, 0.1);
  --success:       #22885c;
  --warning:       #a06f10;
  --purple:        #7452c4;
  --shadow:        0 2px 8px rgba(20, 30, 45, 0.08);
  --shadow-lg:     0 12px 40px rgba(20, 30, 45, 0.15);
}

/* Fixed light-blue chips keep dark text in light mode too */
:root[data-theme="light"] .badge-super { color: #0d1218; }
:root[data-theme="light"] .dl-preview video { background: #10141a; }

/* ================= Responsive ================= */

@media (max-width: 900px) {
  .shell { grid-template-columns: 1fr; }

  .sidebar {
    position: sticky;
    top: 0;
    z-index: 30;
    height: auto;
    max-height: 100dvh;
    overflow-y: auto;
    padding: 0.7rem 1rem;
    border-right: none;
    border-bottom: 1px solid var(--border);
    gap: 0.35rem;
  }
  .sidebar-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    padding: 0.4rem;
    cursor: pointer;
  }
  .sidebar-toggle:hover { color: var(--text); border-color: var(--border-strong); }

  .side-nav, .sidebar-foot { display: none; }
  .sidebar.open .side-nav {
    display: flex;
    padding-top: 0.75rem;
  }
  .sidebar.open .sidebar-foot { display: flex; }

  .main { padding: 1.5rem 1.1rem 3rem; }
  .options { grid-template-columns: 1fr 1fr; }
  .stats-row { grid-template-columns: repeat(3, 1fr); gap: 0.5rem; }
  .stat-value { font-size: 1.1rem; }
  .admin-create-form { grid-template-columns: 1fr; }
}

/* Below ~680px the 640px-wide share card is no longer the limiting factor: the
   viewport is, and its 2rem of padding is width the file names need more. */
@media (max-width: 680px) {
  .bare-top { padding: 0.7rem 0.9rem; gap: 0.6rem; flex-wrap: wrap; }
  .bare-main { padding: 1.5rem 0.85rem 1.5rem; }
  .dl-card { padding: 1.5rem 1.15rem 1.35rem; }
  .card { padding: 1.15rem 1rem; }

  .dl-card h1 { font-size: 1.2rem; }
  .dl-file-head { gap: 0.7rem; margin-bottom: 1rem; }
  .dl-file-head .ficon { width: 44px; height: 44px; border-radius: 11px; }
  .dl-file-head .ficon svg { width: 22px; height: 22px; }
  .dl-file-title h1 { font-size: 1.1rem; }

  /* Fill the screen rather than a fixed pixel box the phone cannot afford. */
  .dl-preview img, .dl-preview video { max-height: 55vh; }
  .dl-preview iframe { height: 60vh; }
  .e2e-text { max-height: 50vh; }

  .queue-item { padding: 0.75rem 0.85rem 0.85rem; }
  .batch-share { padding: 0.85rem 0.9rem 0.9rem; }

  /* The gallery is full-bleed at every size; here the chrome around the stage
     shrinks so more of the screen is left for the file itself. */
  .gallery-box { padding: 0.8rem 0.85rem 0.9rem; gap: 0.5rem; }
  .gallery-nav { width: 34px; height: 34px; }
  .gallery-stage { gap: 0.35rem; }
  .gallery-strip { gap: 0.4rem; }
  .gallery-strip > li { flex-basis: 68px; }
  .gallery-thumb .ficon-svg { width: 20px; height: 20px; }
  .file-item { padding: 0.8rem 0.85rem; gap: 0.7rem; }
  .batch-row { padding: 0.7rem 0.75rem; gap: 0.65rem; }
}

/* ---- drops ---------------------------------------------------------------
   A drop is a share link pointing the other way, so its pieces borrow the
   share panel's shapes rather than inventing new ones: the same boxes, the
   same chips, the same list rhythm. The one place it deliberately looks
   different is the private link, which is the only element in the application
   that, once lost, cannot be recovered from anywhere. */

.share-box {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  margin-bottom: 0.9rem;
}
.share-box-private {
  border-color: var(--warning);
  background: linear-gradient(180deg, rgba(240, 198, 116, 0.06), transparent 60%), var(--surface);
}
.share-label { font-weight: 600; font-size: 0.9rem; }
.share-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.share-url {
  flex: 1 1 18rem;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.82rem;
}
.share-note { margin: 0; font-size: 0.8rem; line-height: 1.45; color: var(--text-muted); }

.opt-wide { grid-column: 1 / -1; }
.opt-wide textarea {
  width: 100%;
  resize: vertical;
  font: inherit;
  font-size: 0.9rem;
}

.drop-note {
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: 0.7rem 0.9rem;
  margin: 0.9rem 0;
}
.drop-note-label {
  display: block;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.25rem;
}
.drop-note p { margin: 0; font-size: 0.9rem; line-height: 1.5; overflow-wrap: anywhere; }

.list-section { margin-top: 2rem; }
.list-title { font-size: 1.05rem; margin: 0 0 0.35rem; }

.drop-list { list-style: none; margin: 1rem 0 0; padding: 0; }
.drop-item {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  margin-bottom: 0.6rem;
  flex-wrap: wrap;
}
.drop-main { display: flex; flex-direction: column; gap: 0.25rem; flex: 1 1 20rem; min-width: 0; }
.drop-name { font-weight: 600; }
.drop-facts { font-size: 0.82rem; display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.drop-facts.muted { color: var(--text-muted); }
.drop-actions { display: flex; gap: 0.4rem; align-items: center; }
.chip-ok { color: var(--success); border-color: var(--success); }

.inbox-public { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }

/* A dropzone that cannot accept anything — no key, or a key that did not open
   the recipient's — must not look like one that can. */
.dz-disabled { opacity: 0.5; pointer-events: none; }

/* ---- inbox: one header per delivery ----
   Only drawn when a drop holds more than one, so a single delivery reads
   exactly like a batch. The sender's name is what the sender typed, and the
   styling deliberately keeps it quieter than a decrypted file name. */
.batch-group {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.7rem 0.85rem 0.55rem;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.batch-group-title { font-weight: 600; font-size: 0.88rem; }
.batch-group-from { font-weight: 400; color: var(--text-muted); }
.batch-group-meta { font-size: 0.78rem; }
.batch-group-message {
  margin: 0.25rem 0 0;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--text);
  overflow-wrap: anywhere;
}

.queue-failed .queue-fill { background: var(--danger); }

@media (max-width: 560px) {
  .main { padding: 1.25rem 0.85rem 2.5rem; }
  .options { grid-template-columns: 1fr; }
  .file-item { flex-wrap: wrap; }
  .list-toolbar { flex-wrap: wrap; }
  .list-toolbar .search-row { flex-basis: 100%; }
  .group-controls { margin-left: auto; }
  .file-sublist { padding: 0.45rem 0.45rem 0.45rem 1rem; }
  /* Same as a row: the actions drop to a line of their own rather than
     squeezing the title to one letter per line. */
  .group-head { flex-wrap: wrap; gap: 0.6rem; padding: 0.8rem 0.85rem; }
  .file-actions { width: 100%; justify-content: flex-end; flex-wrap: wrap; }
  .stats-row { grid-template-columns: 1fr 1fr; }

  /* A 110px label column left roughly 130px for values like
     "application/vnd.openxmlformats-...", so stack the pairs instead: the
     value then gets the full width of the card and is shown in full. */
  .dl-details, .account-info {
    grid-template-columns: 1fr;
    gap: 0;
    padding: 0.85rem 0.95rem;
  }
  .dl-details dt, .account-info dt {
    font-size: 0.76rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-dim);
  }
  .dl-details dd, .account-info dd { margin-top: 0.1rem; }
  .dl-details dd + dt, .account-info dd + dt { margin-top: 0.7rem; }
  .account-info dd { overflow-wrap: anywhere; }

  /* Batch landing rows: the two action buttons plus a 40px icon left the name
     almost nothing (worse in German — "Vorschau"/"Herunterladen"). Give the
     name and its size/type line the whole first row and drop the buttons onto
     a line of their own. */
  .batch-row-actions {
    flex-basis: 100%;
    justify-content: flex-end;
    margin-top: 0.15rem;
  }
  .batch-row-name, .batch-row-sub {
    white-space: normal;
    overflow: visible;
    overflow-wrap: anywhere;
  }

  /* Same story for the share link: the field gets its own line, then Copy/QR. */
  .queue-link { flex-wrap: wrap; }
  .queue-link input { flex-basis: 100%; }

  /* Upload queue: the file name gets the whole first line and wraps in full,
     with the percentage and the cancel button below it. Scoped to .queue so the
     single-file/batch progress bar, which reuses these classes for a short
     status label, keeps its one-line layout. */
  .queue .queue-info { flex-wrap: wrap; gap: 0.35rem 0.75rem; }
  .queue .queue-name { flex-basis: 100%; white-space: normal; overflow-wrap: anywhere; }

  /* Comfortable thumb targets — .btn-sm is otherwise ~26px tall. */
  .btn-sm { padding: 0.5rem 0.8rem; min-height: 38px; }
  .lang-opt { padding: 0.4rem 0.8rem; }
  .lang-opt-icon { padding: 0.4rem 0.7rem; }

  /* Anything below 16px makes iOS Safari zoom the page in on focus, and it
     never zooms back out — a password-protected share link is unusable then. */
  .auth-form input,
  .opt input, .opt select,
  .search-input,
  .settings-form input:not([type=checkbox]),
  .admin-create-form input:not([type=checkbox]),
  .row-action .inline-form input,
  .queue-link input { font-size: 16px; }
}
