/* ═══════════════════════════════════════════════════════════════════════════
   artifact-share — app.css

   Ported from demo.html, which is the reviewed and approved design. Where this
   file and LAYOUT.md disagree, the demo wins; fix the spec, not the port.

   Currently covers the tokens, base, buttons, inputs, and the sign-in screens.
   The shell (sidebar, breadcrumb, toolbar, file table, details panel) lands with
   the file browser — see TODO.md §5.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══ TOKENS ═════════════════════════════════════════════════════════════════
   Two themes, light and dark, and nothing else. No [data-theme] attribute means
   light, which is what somebody who has never chosen gets — an instance is a
   workplace tool opened beside other light documents, and a first visit that
   paints itself black is a surprise nobody asked for.

   **prefers-color-scheme is deliberately not consulted.** There is no third
   "follow the OS" mode: the choice is one switch with two positions, made here
   and remembered, and a page that changes colour because of a setting somebody
   made for reading a phone at night is the confusion that mode was there to
   cause. Every colour goes through a var() — no raw hex in component rules.
   ═════════════════════════════════════════════════════════════════════════ */
:root{
  --bg:#ffffff;      --surface:#f9fafb;   --surface-2:#f3f4f6;
  --hover:#f3f4f6;   --active:#e9ebef;    --sel:#eff4ff;
  --border:#e5e7eb;  --border-2:#d3d7de;
  --fg:#111827;      --fg-2:#4b5563;      --fg-3:#8b95a5;
  --accent:#2563eb;  --accent-fg:#ffffff; --accent-soft:#dbeafe;
  --danger:#dc2626;  --danger-soft:#fee2e2;
  --warn:#b45309;    --warn-soft:#fef3c7;
  --ok:#15803d;      --ok-soft:#dcfce7;
  --shadow-sm:0 1px 2px rgba(16,24,40,.06),0 1px 3px rgba(16,24,40,.10);
  --shadow-md:0 4px 8px -2px rgba(16,24,40,.10),0 2px 4px -2px rgba(16,24,40,.06);
  --shadow-lg:0 12px 16px -4px rgba(16,24,40,.08),0 4px 6px -2px rgba(16,24,40,.03);
  --r:8px; --r-sm:6px;
  color-scheme:light;
}
:root[data-theme="dark"]{ color-scheme:dark;
  --bg:#0f1115;      --surface:#151821;   --surface-2:#1b1f29;
  --hover:#1b1f29;   --active:#232833;    --sel:#16233d;
  --border:#262b37;  --border-2:#343a48;
  --fg:#e8eaee;      --fg-2:#a4adbd;      --fg-3:#6e7889;
  --accent:#3b82f6;  --accent-fg:#ffffff; --accent-soft:#1e3a8a;
  --danger:#f05252;  --danger-soft:#3b1616;
  --warn:#d99a2b;    --warn-soft:#3a2a0c;
  --ok:#3fbf6f;      --ok-soft:#0f2e1c;
  --shadow-sm:0 1px 2px rgba(0,0,0,.4);
  --shadow-md:0 4px 10px -2px rgba(0,0,0,.5);
  --shadow-lg:0 14px 28px -6px rgba(0,0,0,.6);
}
:root[data-theme="light"]{ color-scheme:light }

/* ═══ BASE ══════════════════════════════════════════════════════════════════ */
*{box-sizing:border-box;margin:0;padding:0}
/* min-height, never height. A sticky element can only stick within its
   containing block, so `height:100%` on body caps the top bar at one viewport
   and it scrolls away the moment the page is longer than that. */
html,body{min-height:100%}

/* Zero third-party assets: the font comes from the OS and every icon is inline
   SVG, so the page makes no request to any host but its own. */
:root{
  --font:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',
    Arial,'Noto Sans',sans-serif;
  --font-num:ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation Mono',monospace;
}
body{
  font:400 14px/1.5 var(--font);
  background:var(--bg); color:var(--fg);
  -webkit-font-smoothing:antialiased;
  /* Only background and colour transition, and only here: animating every
     property makes a dense table repaint visibly. */
  transition:background-color .15s,color .15s;
}
svg{display:block;flex:none}
/* `hidden` must win. Several components set display on elements that are also
   toggled with the attribute, and a component's own `display:flex` silently
   beating it is a bug that looks like the JavaScript not running. */
[hidden]{display:none !important}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
input,select{font:inherit;color:inherit;background:none;border:0;outline:0}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{font:400 12.5px/1.5 var(--font-num);background:var(--surface-2);
  padding:1px 5px;border-radius:4px}
:focus-visible{outline:2px solid var(--accent);outline-offset:1px;border-radius:4px}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.mut{color:var(--fg-2)}
.spacer{flex:1}

/* ═══ BUTTONS ═══════════════════════════════════════════════════════════════ */
.btn{
  display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 12px;
  border:1px solid var(--border-2);border-radius:var(--r-sm);
  font-weight:500;font-size:13.5px;color:var(--fg);background:var(--bg);
  white-space:nowrap;transition:background-color .12s,border-color .12s;
}
.btn:hover{background:var(--hover);text-decoration:none}
.btn:active{background:var(--active)}
.btn.pri{background:var(--accent);border-color:var(--accent);color:var(--accent-fg)}
.btn.pri:hover{filter:brightness(1.08)}
.btn.dgr{color:var(--danger);border-color:var(--border-2)}
.btn.dgr:hover{background:var(--danger-soft);border-color:var(--danger)}
.btn.gho{border-color:transparent;background:none}
.btn.gho:hover{background:var(--hover)}
.btn.sm{height:28px;padding:0 9px;font-size:12.5px}
.btn.ico{width:34px;padding:0;justify-content:center}
.btn.ico.sm{width:28px}
.btn[disabled]{opacity:.45;pointer-events:none}
/* An actions cell mixes plain links with buttons wrapped in a <form>, because a
   mutation is always a real form. Both are inline-level boxes, so by default
   they sit on their own text baselines — and an inline-flex box carrying an icon
   reports a different baseline from one carrying only text, which is why
   Restore sat a couple of pixels below Delete forever beside it. Aligning on the
   middle instead lines them up with each other and with the row's text. */
.btn,form.inline{vertical-align:middle}

/* ═══ INPUTS ════════════════════════════════════════════════════════════════ */
.inp{
  display:flex;align-items:center;gap:8px;height:34px;padding:0 10px;
  border:1px solid var(--border-2);border-radius:var(--r-sm);background:var(--bg);
  color:var(--fg-3);
}
.inp:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
/* One focus affordance, never two. The wrapper above already turns its border
   accent and grows a ring, so the global :focus-visible outline would draw a
   second border a pixel outside the first — which reads as a rendering fault
   rather than as focus. Controls with no treatment of their own — buttons,
   links, selects, checkboxes — still get that outline. */
.inp :focus-visible{outline:0}
.inp input{flex:1;min-width:0;color:var(--fg)}
.inp input::placeholder{color:var(--fg-3)}

.chip{
  display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 7px;
  border-radius:999px;font-size:11.5px;font-weight:500;
  background:var(--surface-2);color:var(--fg-2);
}

/* ═══ BANNERS ═══════════════════════════════════════════════════════════════
   Inline and persistent, not a toast. A message about why a sign-in failed must
   still be there when the user looks back at the screen. */
.banner{
  display:flex;gap:9px;align-items:flex-start;padding:10px 12px;margin-bottom:16px;
  border-radius:var(--r-sm);border:1px solid var(--border);
  background:var(--surface-2);color:var(--fg-2);font-size:13px;
}
.banner>svg{margin-top:2px}
.banner.err{background:var(--danger-soft);border-color:var(--danger);color:var(--fg)}

/* ═══ TOASTS ════════════════════════════════════════════════════════════════
   What the last action did. Bottom-right, stacked newest last, over everything
   but a modal — LAYOUT.md §2.10.

   They are rendered by the server, so they appear with JavaScript off; app.js
   only fades them away afterwards. The ✕ is a link to the toast's own id, which
   is why :target hides one: dismissing has to work without script too.

   An error and an anything-with-Undo carry data-keep and are never dismissed on
   a timer — the first because somebody has to be able to read why, the second
   because a six-second window to undo a mistake is not one. */
.toasts{
  position:fixed;right:20px;bottom:20px;z-index:400;
  display:flex;flex-direction:column;gap:10px;
  max-width:min(420px,calc(100vw - 40px));
}
.toast{
  display:flex;align-items:flex-start;gap:10px;padding:11px 12px;
  border:1px solid var(--border);border-left:3px solid var(--ok);
  border-radius:var(--r);background:var(--bg);box-shadow:var(--shadow-md);
  font-size:13px;line-height:1.5;color:var(--fg);
  animation:toast-in .16s ease-out;
}
.toast.err{border-left-color:var(--danger)}
.toast>svg{flex:none;margin-top:1px;color:var(--ok)}
.toast.err>svg{color:var(--danger)}
.toast .tx{flex:1;min-width:0;overflow-wrap:anywhere}
.toast form{flex:none}
.toast .x{flex:none;color:var(--fg-3);margin:-2px -2px 0 0;padding:2px;border-radius:4px}
.toast .x:hover{color:var(--fg);background:var(--hover)}
/* Dismissed: the ✕ points at this toast, so it is its own target. */
.toast:target{display:none}
/* Faded out by app.js, which then removes it — the transition is here so the
   removal has something to animate. */
.toast.gone{opacity:0;transform:translateY(4px);transition:opacity .2s,transform .2s}
@keyframes toast-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media (max-width:720px){
  .toasts{right:12px;left:12px;bottom:12px;max-width:none}
}

.notice{
  display:flex;gap:9px;padding:11px;border-radius:var(--r-sm);background:var(--surface-2);
  font-size:12.5px;color:var(--fg-2);margin-top:20px;
}
.notice>svg{color:var(--fg-3);margin-top:1px}

/* ═══ SIGN-IN SCREENS ═══════════════════════════════════════════════════════ */
.login{position:fixed;inset:0;z-index:500;background:var(--surface);display:none;
  place-items:center;padding:20px;overflow:auto}
/* The sign-in screens have no top bar, so the theme control sits in the corner
   of the screen. It is the one control in the application that JavaScript has to
   carry: there is no account to post the choice to before you have signed in, so
   it is hidden until app.js has set the `js` class. */
.theme-corner{display:none;position:absolute;top:14px;right:14px;z-index:2}
.js .theme-corner{display:inline-flex}
.login.on{display:grid}
.login .box{width:100%;max-width:386px}
.login .lb{display:flex;align-items:center;gap:10px;justify-content:center;margin-bottom:22px}
.login .lb b{font-size:17px;font-weight:600}
.login .cd{background:var(--bg);border:1px solid var(--border);border-radius:12px;
  box-shadow:var(--shadow-sm);padding:26px 24px}
.login h1{font-size:19px;font-weight:600;margin-bottom:4px}
.login .cd>p{color:var(--fg-2);font-size:13.5px;margin-bottom:22px}
.login .btn.pri{width:100%;height:39px;justify-content:center}
.login .alt{text-align:center;font-size:13.5px;color:var(--fg-2);margin-top:19px}
.row-b{display:flex;align-items:center;justify-content:space-between;
  margin:-4px 0 17px;font-size:13px}

.mark{width:32px;height:32px;border-radius:8px;background:var(--accent);
  color:var(--accent-fg);display:grid;place-items:center;font-weight:600;flex:none}
/* Up to four characters go in the same square, so the type shrinks rather than
   the tile growing: a wordmark that changes width with the instance's initials
   would move the search field on every deployment. Classes, not a style
   attribute — style-src has no 'unsafe-inline' and a nonce cannot rescue an
   attribute. markClass in admin_branding.go picks one. */
.mark.m2{font-size:14px;letter-spacing:-.02em}
.mark.m3{font-size:11px;letter-spacing:-.03em}
.mark.m4{font-size:9px;letter-spacing:-.04em}
.mark.preview{margin-bottom:8px}

.fld{margin-bottom:15px}
.fld label{display:block;font-size:13px;font-weight:500;margin-bottom:6px}
.fld .inp{height:38px}

/* ═══ APP SHELL ═════════════════════════════════════════════════════════════
   Ported from demo.html, which is the reviewed implementation of LAYOUT.md §2.

   The shell is a fixed grid that fills the viewport and scrolls internally, the
   way every file manager does: the top bar and the sidebar stay put while the
   listing moves. Pages outside the shell — sign-in, the documentation, the
   admin area — scroll the document normally, which is why body only stops
   scrolling on .app-page.
   ═════════════════════════════════════════════════════════════════════════ */
body.app-page{overflow:hidden;height:100%}
.app{
  height:100vh;display:grid;
  grid-template-rows:56px 1fr;
  grid-template-columns:248px 1fr;
}

/* ── top bar ─────────────────────────────────────────────────────────────── */
.topbar{
  display:flex;align-items:center;gap:12px;height:56px;padding:0 14px;
  border-bottom:1px solid var(--border);background:var(--bg);
  position:sticky;top:0;z-index:60;
}
.app>.topbar{grid-column:1/-1}
.brand{display:flex;align-items:center;gap:9px;width:220px;min-width:0;color:var(--fg)}
.brand:hover{text-decoration:none}
.brand b{font-weight:600;font-size:15px;white-space:nowrap;overflow:hidden}
.brand .mark{width:28px;height:28px;border-radius:7px;font-size:13px}
.brand .mark.m2{font-size:11.5px}
.brand .mark.m3{font-size:9.5px}
.brand .mark.m4{font-size:8px}
.brand .mark:hover{text-decoration:none}
.search{flex:1;max-width:520px}
.search .inp{width:100%}
.top-right{margin-left:auto;display:flex;align-items:center;gap:6px}
.ava{
  width:30px;height:30px;border-radius:50%;background:var(--surface-2);
  border:1px solid var(--border);display:grid;place-items:center;
  font-size:11.5px;font-weight:600;color:var(--fg-2);flex:none;cursor:pointer;
}
.ava:hover{background:var(--hover)}

/* ── sidebar ─────────────────────────────────────────────────────────────── */
.sidebar{
  border-right:1px solid var(--border);background:var(--surface);
  display:flex;flex-direction:column;overflow:hidden;transition:width .16s ease;
}
.sb-scroll{flex:1;overflow-y:auto;padding:10px 8px}
.sb-sec{
  padding:14px 8px 6px;font-size:11px;font-weight:600;letter-spacing:.04em;
  text-transform:uppercase;color:var(--fg-3);white-space:nowrap;overflow:hidden;
}
.sb-item{
  display:flex;align-items:center;gap:10px;width:100%;height:36px;padding:0 9px;
  border-radius:var(--r-sm);color:var(--fg-2);font-weight:500;font-size:13.5px;
  text-align:left;white-space:nowrap;
}
.sb-item:hover{background:var(--hover);color:var(--fg);text-decoration:none}
.sb-item svg{color:var(--fg-3)}
/* The label needs its own class: a bare `.sb-item span` selector outranks
   `.dot{flex:none}` and stretches the colour dot into a pill. */
.sb-item .tx{flex:1;overflow:hidden;text-overflow:ellipsis}
.sb-item[aria-current="page"]{background:var(--sel);color:var(--accent);font-weight:600}
.sb-item[aria-current="page"] svg{color:var(--accent)}
.sb-none{padding:4px 9px 8px;font-size:12.5px;color:var(--fg-3);white-space:normal}
.sb-gap{height:10px}
/* Sized like an icon so it lines up with the SVG nav icons, and so it is still
   the row's icon when the sidebar collapses to a rail. */
.dot{width:16px;height:16px;flex:none;display:grid;place-items:center}
.dot::before{content:'';width:9px;height:9px;border-radius:50%;background:currentColor}
.sb-foot{border-top:1px solid var(--border);padding:12px 14px}
.sb-foot small{font-size:12px;color:var(--fg-3);display:block}
.meter{height:6px;border-radius:999px;background:var(--surface-2);overflow:hidden;margin:7px 0 6px}
.meter i{display:block;height:100%;width:0;background:var(--accent);border-radius:999px}
.meter.over i{background:var(--danger)}

/* ── content ─────────────────────────────────────────────────────────────── */
.content{display:grid;grid-template-columns:1fr auto;overflow:hidden;min-width:0}
/* The pane is the scroll container, not the table wrapper: a scrolling wrapper
   would clip every row menu that opens near the bottom of it. */
.mainpane{display:flex;flex-direction:column;overflow-y:auto;min-width:0}

.head{padding:16px 20px 0;flex:none}
.headrow{display:flex;align-items:flex-start;gap:12px}
.hdr-trash{flex:none;color:var(--fg-2);margin-top:2px}
.hdr-trash:hover{color:var(--fg);text-decoration:none}
.crumbs{display:flex;align-items:center;gap:2px;flex-wrap:wrap;min-height:28px;flex:1;min-width:0}
.crumbs a,.crumbs .here{
  padding:3px 7px;border-radius:var(--r-sm);font-size:19px;font-weight:600;
}
.crumbs a{color:var(--fg-2);display:inline-flex;align-items:center;gap:6px}
.crumbs a svg{color:var(--fg-3)}
.crumbs a:hover{background:var(--hover);color:var(--fg);text-decoration:none}
.crumbs .here{color:var(--fg)}
.crumbs svg{color:var(--fg-3)}

.desc{
  display:flex;align-items:flex-start;gap:7px;margin:5px 0 0;max-width:78ch;
  color:var(--fg-2);font-size:13.5px;line-height:1.55;
}
.desc .txt{border-radius:var(--r-sm);padding:2px 5px;margin:-2px -5px;color:inherit}
.desc .txt:hover{background:var(--hover);text-decoration:none}
.desc.blank .txt{color:var(--fg-3)}
.desc .pen{opacity:0;transition:opacity .12s;color:var(--fg-3);margin-top:3px}
.desc:hover .pen,.desc:focus-within .pen{opacity:1}

.frozen{
  display:flex;gap:8px;align-items:flex-start;margin-top:10px;max-width:78ch;
  padding:9px 11px;border-radius:var(--r-sm);font-size:13px;
  background:var(--warn-soft);color:var(--fg-2);
}
.frozen>svg{color:var(--warn);margin-top:2px;flex:none}

/* ── toolbar and bulk bar ────────────────────────────────────────────────── */
.toolbar{display:flex;align-items:center;gap:8px;padding:14px 20px;flex:none;flex-wrap:wrap}
.toolbar .sp,.bulk .sp{flex:1}
.btn.off{opacity:.45;pointer-events:none}
.seg{display:flex;border:1px solid var(--border-2);border-radius:var(--r-sm);overflow:hidden}
.seg button{min-width:32px;height:32px;display:grid;place-items:center;color:var(--fg-3)}
.seg button:hover{background:var(--hover)}
.seg button[aria-pressed="true"]{background:var(--surface-2);color:var(--fg)}
.seg button+button{border-left:1px solid var(--border)}
.seg.lbl button{
  width:auto;display:flex;align-items:center;gap:6px;padding:0 11px;
  font-size:13px;
}
.seg.wide button{
  flex:1;display:flex;align-items:center;justify-content:center;gap:7px;
  height:38px;font-size:13px;
}

.bulk{
  display:flex;align-items:center;gap:8px;margin:0 20px 14px;padding:8px 10px;
  border:1px solid var(--accent);background:var(--sel);border-radius:var(--r);
  flex:none;flex-wrap:wrap;
}
.bulk .n{font-weight:600;font-size:13.5px;color:var(--accent);margin-right:4px}
/* One of the two raw colours in this file: --accent on --sel is legible in light
   and not quite in dark, and a token for "the accent, but lighter, only here"
   would be a token nothing else could use. The OS-preference twin of this rule
   went with the third theme mode — no attribute now means light, full stop. */
:root[data-theme="dark"] .bulk .n{color:#bfdbfe}
/* Without JavaScript both bars are shown and the bulk bar acts on whatever is
   ticked. With it, the toolbar is replaced the moment anything is selected. */
.js .bulk{display:none}
.js .bulk.on{display:flex}
.js .toolbar.off{display:none}
.hidden-form{display:none}

/* ── file table ──────────────────────────────────────────────────────────── */
.tablewrap{padding:0 20px 28px}
table{width:100%;border-collapse:separate;border-spacing:0}
thead th{
  position:sticky;top:0;background:var(--bg);z-index:5;
  text-align:left;font-size:12px;font-weight:600;color:var(--fg-3);
  padding:0 10px 8px;white-space:nowrap;border-bottom:1px solid var(--border);
}
thead th a{display:inline-flex;align-items:center;gap:4px;color:inherit}
thead th a:hover{color:var(--fg);text-decoration:none}
thead th .ar{opacity:0}
thead th[aria-sort] a{color:var(--fg)}
thead th[aria-sort] .ar{opacity:1}
tbody td{
  padding:0 10px;height:46px;border-bottom:1px solid var(--border);
  font-size:13.5px;color:var(--fg-2);white-space:nowrap;
}
tbody tr:hover td{background:var(--hover)}
tbody tr[aria-selected="true"] td{background:var(--sel)}
tbody tr.kb td{box-shadow:inset 0 0 0 1px var(--accent)}
td.name{color:var(--fg);font-weight:500;width:100%;max-width:0}
.namecell{display:flex;align-items:center;gap:10px;min-width:0}
.namecell .ic{flex:none}
.namecell .t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg)}
.namecell a.t:hover{text-decoration:underline}
.namecell .strong{font-weight:500}
.ic.f{color:var(--accent)}
.ic.d{color:var(--fg-3)}
td.act{text-align:right;width:1%}
td.cbc,th.cbc{width:1%;padding-left:14px}
.rowacts{display:flex;gap:2px;justify-content:flex-end}
/* Row actions fade in on hover but duplicate the bulk bar and the details
   drawer, so nothing here is reachable only by hovering. */
.rowacts{opacity:0;transition:opacity .1s}
tr:hover .rowacts,tr:focus-within .rowacts,tr[aria-selected="true"] .rowacts,
.rowacts:has(details[open]){opacity:1}
.lock{color:var(--warn);flex:none;display:flex}
/* One glyph, two states: an inherited lock is dimmed, because the distinction
   is informative rather than actionable. The tooltip names the folder. */
.lock.inherited{opacity:.6}
.loc{font-size:12px;color:var(--fg-3)}
.right{text-align:right}
.nowrap{white-space:nowrap}
.inline{display:inline}
td .m{font-size:12px;color:var(--fg-3)}

input[type=checkbox]{
  width:16px;height:16px;accent-color:var(--accent);cursor:pointer;
  margin:0;vertical-align:middle;
}

/* ── grid view ───────────────────────────────────────────────────────────── */
.grid{
  padding:0 20px 28px;display:grid;
  grid-template-columns:repeat(auto-fill,minmax(158px,1fr));
  gap:12px;align-content:start;
}
.gcard{
  border:1px solid var(--border);border-radius:var(--r);padding:14px 12px;
  background:var(--bg);text-align:center;position:relative;
}
.gcard:hover{background:var(--hover);border-color:var(--border-2)}
.gcard[aria-selected="true"]{background:var(--sel);border-color:var(--accent)}
.gcard .big{display:grid;place-items:center;height:56px;margin-bottom:8px;color:var(--accent)}
.gcard .nm{
  font-size:13px;font-weight:500;color:var(--fg);display:block;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.gcard .mt{font-size:11.5px;color:var(--fg-3);margin-top:2px}
.gcard .cbw{position:absolute;top:8px;left:8px;opacity:0}
.gcard:hover .cbw,.gcard[aria-selected="true"] .cbw,.gcard:focus-within .cbw{opacity:1}
.gcard .lk{position:absolute;top:9px;right:9px;color:var(--warn)}

/* ── the file viewer ─────────────────────────────────────────────────────────
   Opening a file and downloading it are separate actions; this is what opening
   one looks like. The viewer fills the pane the listing was in, so the file is
   read where the folder was, not in a new tab or a downloads folder. */
.prev-meta{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin-top:6px;font-size:13px;color:var(--fg-3);
}
.prev-desc{
  margin:0 20px 4px;padding:10px 12px;max-width:78ch;font-size:13.5px;
  color:var(--fg-2);background:var(--surface);border-radius:var(--r-sm);
}
.viewer{
  flex:1;min-height:0;display:flex;flex-direction:column;
  padding:0 20px 24px;
}
.viewer.v-image{align-items:center;justify-content:flex-start}
.viewer.v-image img{
  max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--r);
  border:1px solid var(--border);background:var(--surface);
}
.viewer.v-pdf iframe{
  flex:1;width:100%;min-height:520px;border:1px solid var(--border);
  border-radius:var(--r);background:var(--surface);
}
.viewer.v-video video{
  max-width:100%;max-height:100%;border-radius:var(--r);background:#000;
}
.v-text{
  flex:1;overflow:auto;margin:0;padding:16px 18px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r);
}
.v-text code{
  display:block;background:none;border:0;padding:0;white-space:pre;
  font:400 12.5px/1.65 var(--font-num);color:var(--fg);
}
.v-note{
  display:flex;gap:8px;align-items:flex-start;margin-bottom:10px;
  padding:9px 11px;border-radius:var(--r-sm);font-size:13px;
  background:var(--surface-2);color:var(--fg-2);
}
.v-note>svg{color:var(--fg-3);flex:none;margin-top:2px}
.v-box{
  flex:1;display:grid;place-items:center;align-content:center;gap:10px;
  padding:40px 20px;text-align:center;color:var(--fg-3);
  border:1px solid var(--border);border-radius:var(--r);background:var(--surface);
}
.v-box>svg{color:var(--border-2)}
.v-box b{font-size:15px;color:var(--fg);word-break:break-all}
.v-box p{max-width:44ch;font-size:13.5px}
.v-box audio{width:min(420px,100%)}

/* ── the editor ──────────────────────────────────────────────────────────────
   A <textarea> with a highlighted <pre> painted behind it. The two must agree
   on every metric that affects where a glyph lands — font, size, line height,
   letter spacing, padding, tab size, wrapping — or the colour drifts away from
   the text as you scroll. Change one, change both.
   ═════════════════════════════════════════════════════════════════════════ */
.edwrap{flex:1;min-height:0;display:flex;gap:14px;padding:0 20px 20px}
.edpane{flex:1;min-width:0;display:flex;flex-direction:column}
.edwrap[data-mode="split"] .edpane{flex:1 1 50%}
.editor{
  flex:1;min-height:320px;display:flex;overflow:hidden;
  border:1px solid var(--border);border-radius:var(--r);background:var(--surface);
}
.ed-gutter{
  flex:none;width:52px;overflow:hidden;padding:12px 8px 12px 0;
  text-align:right;background:var(--surface-2);
  border-right:1px solid var(--border);color:var(--fg-3);
  font:400 12.5px/1.65 var(--font-num);user-select:none;
}
.ed-gutter i{display:block;font-style:normal}
.ed-stack{position:relative;flex:1;min-width:0}
.ed-hl,.ed-input{
  margin:0;padding:12px 14px;border:0;
  font:400 12.5px/1.65 var(--font-num);
  tab-size:4;white-space:pre;overflow:auto;
  letter-spacing:normal;
}
.ed-hl{
  position:absolute;inset:0;pointer-events:none;color:var(--fg);
  overflow:hidden;
}
.ed-input{
  position:absolute;inset:0;width:100%;height:100%;resize:none;
  background:transparent;color:transparent;caret-color:var(--fg);
}
.ed-input::selection{background:var(--accent-soft);color:transparent}
.ed-input:focus{outline:0}
.editor:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
/* Without the highlighter the textarea has to be readable on its own. */
html:not(.js) .ed-input{color:var(--fg);background:var(--bg)}

.t-com{color:var(--fg-3);font-style:italic}
.t-str{color:var(--ok)}
.t-num{color:var(--warn)}
.t-kw{color:var(--accent);font-weight:500}
.t-tag{color:var(--accent)}
.t-att{color:var(--warn)}

.edpane.preview{overflow:auto;border:1px solid var(--border);border-radius:var(--r);
  background:var(--bg);padding:18px 22px}
.ed-frame{flex:1;width:100%;min-height:320px;border:0;background:#fff}
.dirty{margin-left:8px;font-size:13px;font-weight:500;color:var(--warn)}

/* ── the upload conflict dialog ────────────────────────────────────────────── */
.conflict{max-width:520px}
.conflict .mb p+p{margin-top:10px}
.conflict .fname{font:400 13px/1.5 var(--font-num);word-break:break-all}
.conflict .choices{display:flex;flex-direction:column;gap:10px;margin-top:14px}
.conflict .choices .btn{justify-content:flex-start;height:auto;padding:10px 12px;
  text-align:left;white-space:normal}
.conflict .choices .btn small{display:block;font-weight:400;color:var(--fg-2);
  margin-top:2px;font-size:12.5px}
.conflict .rename{margin-top:12px}

/* ── details drawer ──────────────────────────────────────────────────────── */
.details{
  width:296px;border-left:1px solid var(--border);background:var(--bg);
  display:flex;flex-direction:column;overflow:hidden;
}
.dt-head{
  display:flex;align-items:center;justify-content:space-between;
  padding:14px 16px;border-bottom:1px solid var(--border);
}
.dt-head h3{font-size:14px;font-weight:600}
.dt-body{flex:1;overflow-y:auto;padding:16px}
.dt-hero{
  display:grid;place-items:center;height:78px;background:var(--surface);
  border-radius:var(--r);margin-bottom:14px;color:var(--accent);
}
.dt-name{font-weight:600;font-size:14px;word-break:break-word;margin-bottom:14px}
.kv{
  display:flex;justify-content:space-between;gap:10px;padding:8px 0;
  border-bottom:1px solid var(--border);font-size:13px;
}
.kv dt{color:var(--fg-3);flex:none}
.kv dd{color:var(--fg);text-align:right;overflow:hidden;text-overflow:ellipsis}
.dt-desc{margin-top:14px;font-size:13px;color:var(--fg-2)}
.dt-acts{display:flex;gap:6px;margin-top:14px;flex-wrap:wrap}
.dt-sel{margin-top:16px;font-size:13px;color:var(--fg-3)}

/* ── empty states ────────────────────────────────────────────────────────── */
.empty{flex:1;display:grid;place-items:center;padding:40px;text-align:center;color:var(--fg-3)}
.empty .box{max-width:340px}
/* The illustration only. A bare `.empty svg` also matched the icon inside every
   button below it and pushed it 12px above its own label — which is exactly the
   misalignment this rule was blamed for. Icon buttons centre themselves through
   .btn's flex, and nothing should reach inside them.

   Every rule below that styles "the icon in this block" is scoped the same way,
   for the same reason. */
.empty .box>svg{margin:0 auto 12px;color:var(--border-2)}
.empty h3{font-size:15px;font-weight:600;color:var(--fg);margin-bottom:4px}
.empty p{font-size:13.5px;margin-bottom:16px}
.empty .btn{margin:0 3px}

/* ── panes: trash, account ───────────────────────────────────────────────── */
.pane{flex:1;padding:22px 24px 60px;min-width:0}
.pane.narrow{max-width:720px}
.pane-head{display:flex;align-items:flex-start;gap:14px;margin-bottom:20px;flex-wrap:wrap}
.pane-head h1{font-size:21px;font-weight:600;margin-bottom:3px}
.pane-head p{color:var(--fg-2);font-size:13.5px;max-width:70ch}
.pane-head .sp{flex:1}
.pane-head .back{margin:0 0 8px -9px;color:var(--fg-2)}
.eyebrow{
  display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;
  letter-spacing:.04em;text-transform:uppercase;color:var(--fg-3);margin-bottom:2px;
}
.card-list{border:1px solid var(--border);border-radius:var(--r);overflow:auto;max-width:1000px}
.card-list thead th{background:var(--surface);padding:10px;position:static}
.card-list tbody td{height:52px}
.card-list tbody tr:last-child td{border-bottom:0}

.pane .card{
  border:1px solid var(--border);border-radius:var(--r);padding:18px 20px;
  margin-bottom:16px;text-align:left;
}
.pane .card h2{font-size:15px;font-weight:600;margin-bottom:10px}
.pane .card .lead{font-size:13.5px;color:var(--fg-2);margin-bottom:14px;max-width:70ch}
.pane .card .banner{margin:0 0 14px}
.hint{font-size:12.5px;color:var(--fg-3);margin-top:6px;display:flex;gap:6px;align-items:flex-start}
.hint>svg{flex:none;margin-top:2px}
.fld .inp input:disabled{color:var(--fg-3)}
ul.plain{list-style:none}
ul.plain li{display:flex;align-items:center;gap:9px;padding:6px 0;font-size:13.5px}
.seg-form{max-width:420px}
.sel{
  height:34px;padding:0 8px;border:1px solid var(--border-2);border-radius:var(--r-sm);
  background:var(--bg);font-size:13px;color:var(--fg);
}
.sel.sm{height:30px;width:100%}
.sel.wide{width:100%;margin-top:12px}


/* ── admin tables and quota bars ─────────────────────────────────────────── */
.uname{display:flex;align-items:center;gap:10px}
.uname .ava{width:32px;height:32px;font-size:12px;cursor:default}
.uname b{color:var(--fg)}
.uname .m{font-size:12px;color:var(--fg-3)}
.meter.mini{height:4px;margin:5px 0 0;max-width:150px}
.pane-head.spaced,.spaced{margin-top:34px}
h2.sub,.admin h2.sub{font-size:17px;font-weight:600;margin:0 0 3px;padding-top:0;border-top:0}
.grid-2{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}
.card.danger{border-color:var(--danger)}
.card.danger h3{color:var(--danger)}
.pane .card h3,.docs-body .card h3{font-size:14px;font-weight:600;margin-bottom:8px}
.docs-body .card{
  border:1px solid var(--border);border-radius:var(--r);padding:18px 20px;
  margin-bottom:16px;text-align:left;
}
.docs-body .card h2{font-size:15px;font-weight:600;margin-bottom:10px}
/* .admin h2 gives section headings a rule and a wide margin, which is right for
   a page of prose and wrong inside a card — it drew a line across every panel
   on the storage page. Cards reset it. */
.admin .card h2,.admin .card h3{margin:0 0 10px;padding-top:0;border-top:0}
.admin .card h2{font-size:15px}
.admin .card .lead{margin-bottom:14px}
.admin .card .hint{margin-bottom:12px}
.docs-body .card .lead{font-size:13.5px;color:var(--fg-2);margin-bottom:14px;max-width:70ch}
.docs-body .card .inp{margin-bottom:12px;max-width:340px}
.docs-body .card .btn{margin-top:2px}
.inp.narrow{width:120px}
.inp.sm{height:30px}
form.inline{display:inline-flex;align-items:center;gap:6px}

/* The stacked bar: used inside allocated inside the global quota. Three layers
   rather than three bars, because the question is how they nest. */
.stack{
  position:relative;height:12px;border-radius:999px;background:var(--surface-2);
  overflow:hidden;margin:4px 0 10px;
}
.stack i{position:absolute;left:0;top:0;bottom:0;border-radius:999px}
.stack .alloc{background:var(--accent-soft);z-index:1}
.stack .used{background:var(--accent);z-index:2}
.stack.ceiling{overflow:visible}
.stack.ceiling::after{
  content:'';position:absolute;top:-3px;bottom:-3px;left:90%;
  width:2px;background:var(--danger);z-index:3;
}
.legend{display:flex;gap:14px;flex-wrap:wrap;font-size:12.5px;color:var(--fg-3)}
.legend .key{
  display:inline-block;width:10px;height:10px;border-radius:3px;
  margin-right:5px;vertical-align:-1px;border:1px solid var(--border-2);
}
.legend .key.used{background:var(--accent)}
.legend .key.alloc{background:var(--accent-soft)}
.legend .key.rest{background:var(--surface-2)}

.colors{display:flex;gap:8px;margin-top:14px;border:0}
.colors legend{font-size:12.5px;color:var(--fg-3);margin-bottom:6px}
.swatch{cursor:pointer;display:inline-flex}
.swatch input{position:absolute;opacity:0;width:0;height:0}
.swatch .dot{width:24px;height:24px;border-radius:50%;border:2px solid transparent}
.swatch .dot::before{width:16px;height:16px}
.swatch input:checked+.dot{border-color:currentColor}
.swatch input:focus-visible+.dot{outline:2px solid var(--accent);outline-offset:2px}


/* ── data-driven widths and group colours ────────────────────────────────────
   These exist because the Content-Security-Policy has no 'unsafe-inline' in
   style-src, which blocks `style=` attributes as well as <style> blocks. A
   nonce cannot rescue a style attribute — nonces apply to elements, not
   attributes — so the two things that genuinely vary per row, a bar's width and
   a group's colour, are classes instead.

   The alternative was adding 'unsafe-inline' to style-src for the sake of two
   values. Keeping the policy strict costs a hundred one-line rules once, and
   they compress to nothing.

   Group colours are a fixed palette for the same reason, and a better one: the
   dot exists to tell two rows apart at a glance, and a free colour picker
   reliably produces two groups nobody can distinguish. See groupColors in
   internal/web/admin_groups.go — the two lists must stay in step.
   ═════════════════════════════════════════════════════════════════════════ */
.w0{width:0%}
.w1{width:1%}
.w2{width:2%}
.w3{width:3%}
.w4{width:4%}
.w5{width:5%}
.w6{width:6%}
.w7{width:7%}
.w8{width:8%}
.w9{width:9%}
.w10{width:10%}
.w11{width:11%}
.w12{width:12%}
.w13{width:13%}
.w14{width:14%}
.w15{width:15%}
.w16{width:16%}
.w17{width:17%}
.w18{width:18%}
.w19{width:19%}
.w20{width:20%}
.w21{width:21%}
.w22{width:22%}
.w23{width:23%}
.w24{width:24%}
.w25{width:25%}
.w26{width:26%}
.w27{width:27%}
.w28{width:28%}
.w29{width:29%}
.w30{width:30%}
.w31{width:31%}
.w32{width:32%}
.w33{width:33%}
.w34{width:34%}
.w35{width:35%}
.w36{width:36%}
.w37{width:37%}
.w38{width:38%}
.w39{width:39%}
.w40{width:40%}
.w41{width:41%}
.w42{width:42%}
.w43{width:43%}
.w44{width:44%}
.w45{width:45%}
.w46{width:46%}
.w47{width:47%}
.w48{width:48%}
.w49{width:49%}
.w50{width:50%}
.w51{width:51%}
.w52{width:52%}
.w53{width:53%}
.w54{width:54%}
.w55{width:55%}
.w56{width:56%}
.w57{width:57%}
.w58{width:58%}
.w59{width:59%}
.w60{width:60%}
.w61{width:61%}
.w62{width:62%}
.w63{width:63%}
.w64{width:64%}
.w65{width:65%}
.w66{width:66%}
.w67{width:67%}
.w68{width:68%}
.w69{width:69%}
.w70{width:70%}
.w71{width:71%}
.w72{width:72%}
.w73{width:73%}
.w74{width:74%}
.w75{width:75%}
.w76{width:76%}
.w77{width:77%}
.w78{width:78%}
.w79{width:79%}
.w80{width:80%}
.w81{width:81%}
.w82{width:82%}
.w83{width:83%}
.w84{width:84%}
.w85{width:85%}
.w86{width:86%}
.w87{width:87%}
.w88{width:88%}
.w89{width:89%}
.w90{width:90%}
.w91{width:91%}
.w92{width:92%}
.w93{width:93%}
.w94{width:94%}
.w95{width:95%}
.w96{width:96%}
.w97{width:97%}
.w98{width:98%}
.w99{width:99%}
.w100{width:100%}
.c1{color:#2563eb}
.c2{color:#7c3aed}
.c3{color:#0d9488}
.c4{color:#db2777}
.c5{color:#ea580c}
.c6{color:#65a30d}
.c7{color:#0891b2}

/* ── public share pages ──────────────────────────────────────────────────────
   No app shell: a visitor holding a link has no account here, and a sidebar of
   groups they cannot open would be noise at best. */
.pub{max-width:560px;margin:0 auto;padding:56px 20px 80px}
.pub.wide{max-width:1000px;padding-top:26px}
.pub-box{background:var(--bg);border:1px solid var(--border);border-radius:12px;
  box-shadow:var(--shadow-sm);padding:26px 24px}
.pub-box h1{font-size:19px;font-weight:600;margin-bottom:6px}
.pub-box p{color:var(--fg-2);font-size:13.5px;margin-bottom:14px}
.pub .lb{display:flex;align-items:center;gap:10px;justify-content:center;margin-bottom:22px}
.pub .lb b{font-size:17px;font-weight:600}
.pub .lb.sm{margin:0;justify-content:flex-start}
.pub .lb.sm b{font-size:15px}
.pub .lb.sm .mark{width:28px;height:28px;border-radius:7px;font-size:13px}
.pub .lb.sm .mark.m2{font-size:11.5px}
.pub .lb.sm .mark.m3{font-size:9.5px}
.pub .lb.sm .mark.m4{font-size:8px}
.pub-head{display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  padding-bottom:16px;margin-bottom:18px;border-bottom:1px solid var(--border)}
.pub-head .crumbs{flex:0 1 auto}
.pub-head .crumbs a,.pub-head .crumbs .here{font-size:16px}
.pub-head .sp{flex:1}
.pub-meta{font-size:13px;color:var(--fg-3);margin-bottom:14px}
.pub-foot{display:flex;gap:8px;align-items:flex-start;margin-top:22px;
  font-size:12.5px;color:var(--fg-3)}
.pub-foot>svg{flex:none;margin-top:2px}
.pub .card-list{max-width:none}
.seg a{
  display:inline-flex;align-items:center;padding:0 11px;height:32px;
  font-size:13px;color:var(--fg-3);
}
.seg a:hover{background:var(--hover);text-decoration:none}
.seg a[aria-pressed="true"]{background:var(--surface-2);color:var(--fg)}
.seg a+a{border-left:1px solid var(--border)}

/* The rendered Markdown column reuses the documentation's prose rules. */
.prose.md{max-width:80ch;margin:0 auto}
.tokencell{font:400 12px/1.5 var(--font-num);word-break:break-all;color:var(--fg)}

/* ── the share form ────────────────────────────────────────────────────────── */
.check.big{align-items:flex-start;gap:10px;margin-bottom:6px;font-size:13.5px}
.check.big span{line-height:1.5;color:var(--fg-2)}
.check.big b{color:var(--fg)}
.sub-field{margin:0 0 16px 26px;max-width:420px}
.sub-field label{display:block;font-size:13px;font-weight:500;margin-bottom:6px}
.sub-field .inp{margin-bottom:8px}
.sub-field .inp.narrow{width:110px}
.card.created{border-color:var(--ok)}
.card.created h2{display:flex;align-items:center;gap:8px;color:var(--ok)}
.reveal{
  font:400 13px/1.6 var(--font-num);background:var(--surface-2);
  border:1px solid var(--border);border-radius:var(--r-sm);
  padding:11px 13px;margin-bottom:14px;word-break:break-all;user-select:all;
}

/* A disclosure inside a settings page, for a block of fields most people never
   need to open. */
.pane-details{margin:0 0 18px}
.pane-details>summary{
  cursor:pointer;font-size:13.5px;font-weight:500;color:var(--fg-2);
  padding:8px 0;list-style:revert;
}
.pane-details>summary:hover{color:var(--fg)}
.pane-details[open]>summary{margin-bottom:10px}

/* ── dropdown menus ──────────────────────────────────────────────────────────
   A <details> element, so every menu opens, closes, and is keyboard-operable
   with no JavaScript at all. JS only adds click-outside and Escape. */
.pop{position:relative;display:inline-block}
.pop>summary{list-style:none;cursor:pointer}
.pop>summary::-webkit-details-marker{display:none}
.pop>summary::marker{content:''}
.menu{
  position:absolute;top:calc(100% + 6px);left:0;z-index:200;min-width:224px;padding:5px;
  background:var(--bg);border:1px solid var(--border);border-radius:var(--r);
  box-shadow:var(--shadow-lg);
}
.pop.right>.menu{left:auto;right:0}
.menu button,.menu a{
  display:flex;align-items:center;gap:10px;width:100%;padding:7px 9px;
  border-radius:var(--r-sm);font-size:13.5px;color:var(--fg-2);text-align:left;
}
.menu button:hover,.menu a:hover{background:var(--hover);color:var(--fg);text-decoration:none}
.menu button svg,.menu a svg{color:var(--fg-3);flex:none}
/* A destructive entry is a link now — it opens the shared confirmation rather
   than submitting — so both element types have to carry the danger colour. */
.menu button.dgr,.menu a.dgr{color:var(--danger)}
.menu button.dgr svg,.menu a.dgr svg{color:var(--danger)}
.menu button.dgr:hover,.menu a.dgr:hover{background:var(--danger-soft);color:var(--danger)}
.menu button[disabled]{opacity:.45;pointer-events:none}
.menu hr{border:0;border-top:1px solid var(--border);margin:5px 0}
.menu-head{padding:7px 9px 3px;font-size:12.5px;line-height:1.45}
.menu-head b{display:block;font-size:13.5px;color:var(--fg)}
.menu-head span{color:var(--fg-3);word-break:break-all}
.menu-head .chip{margin-top:5px}
.menu-note{
  display:flex;gap:7px;align-items:flex-start;padding:7px 9px;
  font-size:12.5px;color:var(--fg-3);white-space:normal;
}
.menu-note>svg{color:var(--warn);flex:none;margin-top:1px}
.menu-form{padding:6px 9px 9px;display:flex;flex-direction:column;gap:7px}
.menu-form .inp.sm{height:30px}
.menu-form textarea{
  min-height:62px;resize:vertical;padding:7px 9px;font:inherit;font-size:13px;
  border:1px solid var(--border-2);border-radius:var(--r-sm);
  background:var(--bg);color:var(--fg);outline:0;
}
/* Its own border and ring, so no outline on top — see .inp above. */
.menu-form textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.menu-form textarea:focus-visible{outline:0}
.menu-form .btn{justify-content:center}

/* A confirmation inside a menu. Two deliberate clicks for something
   destructive, without a hidden dialog per row — <details> gives it to us for
   nothing, and it works with JavaScript off. */
.btn.on{background:var(--sel);border-color:var(--accent);color:var(--accent)}

/* ── modals ──────────────────────────────────────────────────────────────────
   :target rather than <dialog>, because a <dialog> can only be opened from
   JavaScript and every mutating action has to work without it. */
.scrim{
  position:fixed;inset:0;z-index:300;background:rgba(16,24,40,.45);
  display:none;place-items:center;padding:20px;overflow:auto;
}
.scrim:target{display:grid}
.modal{
  width:100%;max-width:440px;background:var(--bg);border:1px solid var(--border);
  border-radius:12px;box-shadow:var(--shadow-lg);overflow:hidden;
  animation:pop .14s ease-out;
}
@keyframes pop{from{opacity:0;transform:translateY(6px) scale(.99)}to{opacity:1}}
.modal h2{font-size:16px;font-weight:600;padding:18px 18px 0}
.modal .mb{padding:12px 18px 18px;color:var(--fg-2);font-size:13.5px}
.modal .mb .inp{height:38px;margin-top:12px}
.modal .mb p+p{margin-top:9px}
.modal .mb textarea{
  width:100%;margin-top:12px;min-height:82px;resize:vertical;padding:9px 10px;
  border:1px solid var(--border-2);border-radius:var(--r-sm);background:var(--bg);
  color:var(--fg);font:inherit;font-size:13.5px;outline:0;
}
.modal .mb textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.modal .mb textarea:focus-visible{outline:0}
.modal .mb input.file{
  display:block;width:100%;margin-top:14px;font-size:13px;color:var(--fg-2);
}
.modal .mf{
  display:flex;justify-content:flex-end;gap:8px;padding:14px 18px;
  background:var(--surface);border-top:1px solid var(--border);
}

/* ── upload progress and drop overlay ────────────────────────────────────── */
.uploads{
  position:fixed;right:18px;bottom:18px;z-index:390;width:328px;background:var(--bg);
  border:1px solid var(--border);border-radius:var(--r);box-shadow:var(--shadow-lg);
  overflow:hidden;
}
.up-head{
  display:flex;align-items:center;gap:8px;padding:11px 13px;
  border-bottom:1px solid var(--border);font-size:13.5px;font-weight:600;
}
.up-head .sp{flex:1}
.up-list{max-height:216px;overflow-y:auto;padding:5px 0}
.up-row{padding:8px 13px;font-size:13px}
.up-row .l{display:flex;align-items:center;gap:8px;margin-bottom:6px}
.up-row .l .n{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg)}
.up-row .l .p{font-size:11.5px;color:var(--fg-3)}
.bar{height:4px;border-radius:999px;background:var(--surface-2);overflow:hidden}
.bar i{display:block;height:100%;background:var(--accent);transition:width .2s}
.up-row.done .bar i{background:var(--ok)}
.up-row.failed .bar i{background:var(--danger)}

.drop{position:fixed;inset:0;z-index:350;display:grid;place-items:center;
  background:rgba(37,99,235,.06)}
.drop[hidden]{display:none}
.drop .in{
  border:2px dashed var(--accent);border-radius:14px;background:var(--bg);
  padding:26px 34px;text-align:center;box-shadow:var(--shadow-lg);
}
.drop .in svg{margin:0 auto 10px;color:var(--accent)}
.drop .in b{display:block;font-size:15px;margin-bottom:3px}
.drop .in span{font-size:13px;color:var(--fg-2)}

/* ── banner extras ───────────────────────────────────────────────────────── */
.app .banner{margin:0 20px 12px}
.banner.ok{background:var(--ok-soft);border-color:var(--ok)}
.banner .undo{margin-left:auto}
.chip.ok{background:var(--ok-soft);color:var(--ok)}
.chip.warn{background:var(--warn-soft);color:var(--warn)}
.chip.dgr{background:var(--danger-soft);color:var(--danger)}
.chip.acc{background:var(--accent-soft);color:var(--accent)}
:root[data-theme="dark"] .chip.acc{color:#bfdbfe}
.count{
  min-width:19px;height:19px;padding:0 5px;border-radius:999px;
  background:var(--accent);color:var(--accent-fg);
  font-size:11px;font-weight:600;display:grid;place-items:center;
}
.count.sub{background:var(--surface-2);color:var(--fg-2);font-weight:500}

/* ── bare pages ──────────────────────────────────────────────────────────
   The error page renders without the shell: whatever went wrong, a sidebar
   full of links the caller may not be allowed to follow is not the answer. */
.shell{max-width:760px;margin:0 auto;padding:32px 20px}
.shell.centered{text-align:center;padding-top:80px}
.ph{font-size:19px;font-weight:600;margin-bottom:16px}

/* ── responsive ──────────────────────────────────────────────────────────── */
@media (max-width:1000px){
  .app{grid-template-columns:200px 1fr}
  .brand{width:172px}
  .details{display:none}
  .colhide{display:none}
}

/* Narrow: the sidebar becomes a scrolling strip above the content rather than a
   rail of unlabelled icons. It only lists groups, so a row of labelled chips
   says the same thing in the space available. */
@media (max-width:720px){
  .app{grid-template-columns:1fr;grid-template-rows:56px auto 1fr}
  .brand{width:auto}
  .sidebar{border-right:0;border-bottom:1px solid var(--border)}
  .sb-scroll{
    display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;
    padding:8px 12px;
  }
  .sb-sec{padding:0 2px 0 0}
  .sb-item{width:auto;height:32px;flex:none}
  .sb-none{padding:0;white-space:nowrap}
  .sb-foot{display:none}
}
@media (max-width:640px){
  .search{display:none}
  .colhide2{display:none}
}
@media (prefers-reduced-motion:reduce){*{transition:none !important;animation:none !important}}

/* ═══ DOCUMENTATION & ADMIN ═════════════════════════════════════════════════
   Both are a fixed left rail plus a reading column. The documentation renders
   whatever CommonMark produces, so .prose styles element types rather than a
   fixed set of components.

   Spacing is deliberately generous. These are pages people read rather than
   scan, and text pressed against a border is the single thing that makes a
   documentation page feel unfinished.
   ═════════════════════════════════════════════════════════════════════════ */
.docs{display:flex;align-items:flex-start;gap:0;min-height:calc(100vh - 57px)}

/* ── left rail ─────────────────────────────────────────────────────────────
   Hierarchical: a section label, then its pages indented beneath it, so the
   shape of the documentation is visible without opening anything. */
.docs-nav{
  width:264px;flex:none;padding:26px 16px 48px;
  border-right:1px solid var(--border);background:var(--surface);
  position:sticky;top:57px;overflow:auto;
  /* Fill the viewport even when the page is short, or the rail's background
     stops halfway down and the layout looks broken. */
  height:calc(100vh - 57px);
}
.docs-home{
  display:flex;align-items:center;gap:8px;
  padding:8px 10px;margin-bottom:20px;border-radius:var(--r-sm);
  font-size:14px;font-weight:600;color:var(--fg);
}
.docs-home svg{color:var(--fg-3)}
.docs-home:hover{background:var(--hover);text-decoration:none}
.docs-home.on{background:var(--sel);color:var(--accent)}
.docs-home.on svg{color:var(--accent)}

.docs-sec{
  padding:0 10px;margin:22px 0 7px;font-size:10.5px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;color:var(--fg-3);
}
.docs-nav ul{list-style:none;margin:0;padding:0 0 0 10px;border-left:1px solid var(--border)}
.docs-nav li a{
  display:block;padding:6px 10px;margin-left:-1px;border-radius:0 var(--r-sm) var(--r-sm) 0;
  font-size:13.5px;line-height:1.35;color:var(--fg-2);
  border-left:2px solid transparent;
}
.docs-nav li a:hover{background:var(--hover);color:var(--fg);text-decoration:none}
.docs-nav li a.on{
  background:var(--sel);color:var(--accent);font-weight:500;
  border-left-color:var(--accent);
}

/* ── reading column ───────────────────────────────────────────────────────── */
.docs-body{flex:1;min-width:0;padding:46px 56px 96px;max-width:900px}
.prose{max-width:70ch}

.prose h1{
  font-size:30px;font-weight:650;line-height:1.2;letter-spacing:-.015em;
  margin-bottom:20px;
}
.prose h2{
  font-size:20px;font-weight:620;line-height:1.3;letter-spacing:-.01em;
  margin:44px 0 14px;padding-top:20px;border-top:1px solid var(--border);
}
.prose h3{font-size:16px;font-weight:620;margin:30px 0 10px}
.prose h1+h2{border-top:0;padding-top:0;margin-top:30px}
.prose p{margin-bottom:16px;line-height:1.65}
.prose ul,.prose ol{margin:0 0 18px 24px}
.prose li{margin-bottom:7px;line-height:1.6}
.prose li>ul,.prose li>ol{margin-top:7px;margin-bottom:8px}
.prose strong{font-weight:620}
.prose hr{border:0;border-top:1px solid var(--border);margin:38px 0}
.prose a{text-decoration:underline;text-underline-offset:2px;text-decoration-thickness:1px}
.prose>*:last-child{margin-bottom:0}

.prose code{
  font:400 12.5px/1.5 var(--font-num);background:var(--surface-2);
  border:1px solid var(--border);padding:1px 5px;border-radius:4px;
}
.prose pre{
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r);padding:16px 18px;margin-bottom:20px;overflow-x:auto;
}
.prose pre code{background:none;border:0;padding:0;font-size:12.5px;line-height:1.6}

/* In this documentation a blockquote is nearly always a "> **Planned.**"
   status marker, so it reads as a callout rather than as a quotation. */
.prose blockquote{
  border:1px solid var(--warn);border-left-width:3px;background:var(--warn-soft);
  padding:13px 16px;margin:0 0 20px;border-radius:0 var(--r-sm) var(--r-sm) 0;
  color:var(--fg);
}
.prose blockquote p{margin-bottom:8px}
.prose blockquote p:last-child{margin-bottom:0}

.prose table,.tbl{
  border-collapse:separate;border-spacing:0;width:100%;margin-bottom:22px;
  font-size:13.5px;border:1px solid var(--border);border-radius:var(--r);
  overflow:hidden;
}
.prose table{display:table}
.prose th,.prose td,.tbl th,.tbl td{
  padding:10px 14px;text-align:left;vertical-align:top;
  border-bottom:1px solid var(--border);
  /* Both are reset from the file table's `tbody td`, which is one line high and
     never wraps. That is right for a row of files and wrong for a cell of prose:
     with nowrap the cell's minimum width is the whole sentence, so the table
     grows past its card and pushes the page sideways instead of wrapping. */
  white-space:normal;height:auto;
}
.prose tr:last-child td,.tbl tbody tr:last-child td{border-bottom:0}
.prose th,.tbl th{background:var(--surface);font-weight:620;color:var(--fg)}
.tbl tbody th{background:none;width:180px}
/* A wide table scrolls inside its own box rather than pushing the page sideways. */
.prose .tw{overflow-x:auto}

/* ── documentation index ───────────────────────────────────────────────────
   The index is rendered as a hierarchy of sections and cards, built from the
   index file's own structure, rather than as the flat wall of link rows the
   raw Markdown tables would produce. */
.prose-intro{margin-bottom:12px}
.prose-intro h1{margin-bottom:14px}

.docs-group{margin-top:40px}
.docs-group-h{
  font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--fg-3);margin-bottom:14px;
}
.doc-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(268px,1fr));gap:14px}
.doc-card{
  display:flex;flex-direction:column;gap:6px;padding:16px 18px;
  border:1px solid var(--border);border-radius:var(--r);background:var(--bg);
  color:var(--fg);transition:border-color .12s,background-color .12s;
}
.doc-card:hover{border-color:var(--accent);background:var(--surface);text-decoration:none}
.doc-card-t{font-size:14.5px;font-weight:600;color:var(--fg)}
.doc-card:hover .doc-card-t{color:var(--accent)}
.doc-card-b{font-size:13px;line-height:1.5;color:var(--fg-2)}

/* ── admin ─────────────────────────────────────────────────────────────────
   Shares the rail and the reading column, and reuses .prose's table styling
   through .tbl so the two sections do not drift apart visually. */
.docs-body.admin{max-width:1080px}
.admin h1.ph{font-size:26px;font-weight:650;letter-spacing:-.015em;margin-bottom:10px}
.admin h2{
  font-size:16px;font-weight:620;margin:38px 0 12px;padding-top:18px;
  border-top:1px solid var(--border);
}
.admin .lead{color:var(--fg-2);font-size:14px;line-height:1.6;max-width:68ch;margin-bottom:26px}
.admin .hint{color:var(--fg-2);font-size:12.5px;line-height:1.5;margin-top:6px;max-width:62ch}
.head-row{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:10px}
.head-row .ph{margin-bottom:0}
.r{text-align:right}
.nowrap{white-space:nowrap}
.sm{font-size:12.5px}
.inline{display:inline-flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}
.plain{list-style:none;margin:0}
.plain li{padding:6px 0;border-bottom:1px solid var(--border)}
.plain li:last-child{border-bottom:0}

.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:14px;margin-bottom:8px}
.card{
  padding:18px;border:1px solid var(--border);border-radius:var(--r);background:var(--bg);
}
.card.attn{border-color:var(--warn);background:var(--warn-soft)}
.card-n{font-size:28px;font-weight:650;line-height:1.1;letter-spacing:-.02em}
.card-t{font-size:13.5px;font-weight:600;margin:2px 0 6px}
.card p{font-size:12.5px;color:var(--fg-2);line-height:1.5;margin-bottom:12px}

.pill{
  display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:999px;
  font-size:11.5px;font-weight:600;background:var(--surface-2);color:var(--fg-2);
  white-space:nowrap;
}
.pill.ok{background:var(--ok-soft);color:var(--ok)}
.pill.warn{background:var(--warn-soft);color:var(--warn)}
.pill.dgr{background:var(--danger-soft);color:var(--danger)}

.form{max-width:600px}
.form .fld{margin-bottom:18px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.actions{display:flex;gap:10px;margin-top:24px}
.check,.radio{display:flex;align-items:flex-start;gap:9px;font-size:13.5px;cursor:pointer}
.check input,.radio input{width:15px;height:15px;margin-top:2px;flex:none;accent-color:var(--accent)}
.radios{display:flex;flex-direction:column;gap:12px;margin-bottom:8px}
.radio span{line-height:1.5;color:var(--fg-2)}
.radio b{color:var(--fg)}

/* The generated password, shown exactly once. It is stored only as a hash, so
   this is the single opportunity to copy it — hence the prominence. */
.reveal{
  border:1px solid var(--accent);background:var(--accent-soft);
  border-radius:var(--r);padding:18px 20px;margin-bottom:26px;
}
.reveal-h{display:flex;align-items:center;gap:8px;font-size:13.5px;font-weight:600;margin-bottom:12px}
.reveal-pw{
  display:block;font:600 18px/1.4 var(--font-num);letter-spacing:.01em;
  background:var(--bg);border:1px solid var(--border-2);border-radius:var(--r-sm);
  padding:12px 14px;margin-bottom:12px;user-select:all;word-break:break-all;
}
.reveal p{font-size:12.5px;color:var(--fg-2);line-height:1.5}

.note-box{
  display:flex;gap:11px;padding:14px 16px;margin-bottom:24px;
  border:1px solid var(--border);border-radius:var(--r);background:var(--surface);
  font-size:13px;line-height:1.55;color:var(--fg-2);
}
.note-box svg{color:var(--fg-3);margin-top:2px}
.note-box b{color:var(--fg)}

/* A sentence explaining the table above it — the overcommit total under a member
   list, what the trash is holding, what closing sign-ups does not do. Lighter
   than .note-box, which is a panel: this is a footnote, so it gets the icon
   column and the breathing room and no border. It had neither for a while and
   sat flush against the table above it looking like a rendering accident. */
.note{
  display:flex;gap:9px;align-items:flex-start;margin:14px 0 26px;
  font-size:13px;line-height:1.55;color:var(--fg-2);max-width:78ch;
}
.note>svg{flex:none;margin-top:2px;color:var(--fg-3)}
.note b{color:var(--fg);font-weight:600}

@media (max-width:1000px){
  .docs{display:block}
  .docs-nav{
    width:auto;position:static;height:auto;
    border-right:0;border-bottom:1px solid var(--border);padding:18px 20px 24px;
  }
  .docs-body,.docs-body.admin{padding:30px 22px 72px;max-width:none}
  .grid2{grid-template-columns:1fr}
}
