/* ProjectHub on the Bunzl Tech design system. Tokens are vendored in
   bunzl_tokens.css; everything here either aliases them or styles a component
   the brand does not cover. Light is the base. Dark applies when the OS asks
   for it, unless the operator picked light; an explicit choice wins either way. */
:root {
  color-scheme: light;
  --bg: var(--surface-band); --fg: var(--text-body); --muted: var(--text-muted);
  --line: var(--border-default); --card: var(--surface-card);
  --accent: var(--color-primary); --on-accent: var(--text-on-primary);
  /* Status is its own system. The brand hue stays on interaction — links,
     buttons, focus — and never means anything about state; these four mean
     state and are never used for a control. All four sit on one perceptual
     ring (OKLCH L .52, equal chroma) so no hue shouts louder than another by
     accident, and each clears 4.5:1 as text. Never the only channel: every
     one carries a Lucide glyph too. See ADR 0021. */
  --good-ink:#007e3f; --good-edge:#0ea053; --good-wash:#e3fbe8;
  --bad-ink:#b33832;  --bad-edge:#d5584f;  --bad-wash:#ffefed;
  --attn-ink:#905c00; --attn-edge:#b67700; --attn-wash:#fff1e1;
  --live-ink:#00729c; --live-edge:#0092c6; --live-wash:#e7f6ff;
  --err: var(--bad-ink);
}
/* The brand has no dark mode. These are the navy family: page and card are
   brand blue darkened past --color-hero-deep, accent is --color-brand-tint-3. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark;
    --bg: #000b14; --card: var(--color-hero-deep); --fg: var(--color-brand-tint-1);
    --muted: #8ea6bb; --line: #12405f; --accent: var(--color-brand-tint-3);
    --on-accent: var(--color-hero-deep);
    --good-ink:#6cdc90; --good-edge:#32b364; --good-wash:#12361e;
    --bad-ink:#ffa69b;  --bad-edge:#ea6b60;  --bad-wash:#47211d;
    --attn-ink:#fcaf41; --attn-edge:#cf8700; --attn-wash:#402804;
    --live-ink:#6dceff; --live-edge:#00a5e0; --live-wash:#033247; }
}
:root[data-theme="dark"] { color-scheme: dark;
  --bg: #000b14; --card: var(--color-hero-deep); --fg: var(--color-brand-tint-1);
  --muted: #8ea6bb; --line: #12405f; --accent: var(--color-brand-tint-3);
  --on-accent: var(--color-hero-deep);
  --good-ink:#6cdc90; --good-edge:#32b364; --good-wash:#12361e;
  --bad-ink:#ffa69b;  --bad-edge:#ea6b60;  --bad-wash:#47211d;
  --attn-ink:#fcaf41; --attn-edge:#cf8700; --attn-wash:#402804;
  --live-ink:#6dceff; --live-edge:#00a5e0; --live-wash:#033247; }

* { box-sizing: border-box; border-color: var(--line); }
html { -webkit-font-smoothing: antialiased; }
body { margin:0; background:var(--bg); color:var(--fg);
  font-family:var(--font-sans); font-size:var(--text-sm);
  line-height:var(--leading-normal); letter-spacing:var(--tracking-tight); }
p, li, dd { line-height:var(--leading-relaxed); }
table, .task, .period, .logs { line-height:1.45; }
/* 1280px is the brand container; the gutters are its 32px. */
main { max-width:var(--container-max); margin:0 auto;
  padding:var(--space-6) var(--container-padding) var(--space-16); }
a { color:var(--accent); text-decoration:none; }
a:hover { text-decoration:underline; text-underline-offset:4px; }
code { font-family:var(--font-mono); font-size:var(--text-xs); line-height:1.4;
  background:color-mix(in srgb, var(--fg) 7%, transparent);
  padding:.1em .35em; border-radius:var(--radius-sm); }
h1 { font-size:var(--text-2xl); font-weight:var(--weight-bold); margin:.2rem 0 .4rem; }
h2 { font-size:var(--text-base); font-weight:var(--weight-semibold); margin:0 0 .6rem; }
.muted { color:var(--muted); font-size:var(--text-sm); }

/* The brand header: white at 90% over a 24px blur, one hairline rule, no shadow.
   96px is the marketing height; an operator screen keeps it compact. */
.topbar { position:sticky; top:0; z-index:30;
  display:flex; justify-content:space-between; align-items:center; gap:var(--space-4);
  padding:.65rem var(--container-padding); border-bottom:1px solid var(--line);
  background:color-mix(in srgb, var(--card) 90%, transparent);
  backdrop-filter:blur(var(--blur-header)); }
.topbar .brand { display:inline-flex; align-items:center; gap:.5rem;
  font-weight:var(--weight-medium); text-transform:var(--type-wordmark-transform);
  letter-spacing:var(--tracking-normal); text-decoration:none; color:var(--fg); }
.topbar .brand:hover { text-decoration:none; }
/* The Z glyph, not the BZL lockup: the lockup's B and L are live text in
   Helvetica Now Micro, which is licensed and not shipped.
   Blue on light, white on dark — the two supplied colourways, never recoloured. */
.topbar .brand .mark { height:.95rem; width:auto; display:block; }
.topbar .brand .mark-dark { display:none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .topbar .brand .mark-light { display:none; }
  :root:not([data-theme="light"]) .topbar .brand .mark-dark { display:block; }
}
:root[data-theme="dark"] .topbar .brand .mark-light { display:none; }
:root[data-theme="dark"] .topbar .brand .mark-dark { display:block; }
.topbar nav { display:flex; align-items:center; gap:var(--space-4); }
.topbar nav .linkish { text-transform:var(--type-nav-transform);
  letter-spacing:var(--tracking-wide); font-size:var(--text-xs);
  font-weight:var(--weight-medium); text-decoration:none; color:var(--fg); }
.topbar nav .linkish:hover { text-decoration:underline; text-underline-offset:4px; }
/* Sign out is a button_to, so it arrives as input[type=submit] and would
   otherwise pick up the filled-button fill. The brand header is text links. */
.topbar nav input[type=submit].linkish { background:none; border:none;
  padding:0; box-shadow:none; }
.who { font-size:var(--text-xs); color:var(--muted); }
.role { border:1px solid var(--line); border-radius:var(--radius-full);
  padding:0 .45em; margin-left:.25em; }

.flash { padding:.6rem .8rem; border:1px solid var(--line);
  border-radius:var(--radius-field); margin-bottom:1rem; font-size:var(--text-sm); }
.flash.notice { border-color:var(--good-edge); background:var(--good-wash); color:var(--good-ink); }
.flash.alert  { border-color:var(--bad-edge); background:var(--bad-wash); color:var(--bad-ink); }

.page-head { display:flex; justify-content:space-between; align-items:flex-start;
  gap:1rem; flex-wrap:wrap; margin-bottom:1rem; }
.page-head .actions { display:flex; gap:.5rem; flex:0 0 auto; padding-top:1.2rem; }
.page-head p { margin:.2rem 0; }

/* The brand is flat: 1px border, no shadow, no hover lift. */
.card { background:var(--card); border:1px solid var(--line);
  border-radius:var(--radius); padding:1rem; margin:0 0 1rem; }
/* Seven cards of equal weight is no hierarchy at all. A run has one subject —
   its tasks and its checks — and the rest is reference you consult. */
.card.quiet { background:none; border:none; border-top:1px solid var(--line);
  border-radius:0; padding:1rem 0 0; margin-top:1.5rem; }
.card.quiet > h2 { color:var(--muted); font-size:var(--text-xs);
  text-transform:uppercase; letter-spacing:var(--tracking-wide); }
/* A card that is itself an exception marks it on the left edge, the way a task
   and a period do. A full amber frame competes with whatever the card holds —
   and where the rows already carry their own state it says it twice. */
.card.warn { border-left:3px solid var(--attn-edge); }
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:1rem; }
.tasks { font-size:var(--text-sm); line-height:2; }

.badge { display:inline-block; font-size:var(--text-xs); text-transform:uppercase;
  letter-spacing:var(--tracking-wide); border:1px solid var(--line);
  border-radius:var(--radius-full); padding:.05em .5em; margin-left:.3em;
  color:var(--muted); vertical-align:middle; }
/* A check's severity says how bad a failure would be, so it is metadata and
   stays neutral. Only the result carries colour — see the .status rules. */
.badge.sev { font-weight:var(--weight-semibold); }
.badge.gate, .badge.paused, .badge.warn, .badge.warning {
  border-color:var(--attn-edge); color:var(--attn-ink); background:var(--attn-wash); }
.badge.on { border-color:var(--good-edge); color:var(--good-ink); background:var(--good-wash); }
.badge.error { border-color:var(--bad-edge); color:var(--bad-ink); background:var(--bad-wash); }

.status { font-size:var(--text-xs); font-weight:var(--weight-semibold);
  text-transform:uppercase; letter-spacing:var(--tracking-wide);
  white-space:nowrap; }
/* The glyph is masked, not drawn, so it inherits the status colour and there is
   one rule per state instead of one file per state per theme. */
.status.success::before, .status.sent::before, .status.passed::before,
.status.failed::before, .status.cancelled::before,
.status.waiting::before, .status.paused::before, .status.needs-person::before,
.status.missing::before, .status.soft-failed::before,
.status.running::before, .status.queued::before, .status.pending::before {
  content:""; display:inline-block; width:.9em; height:.9em; margin-right:.35em;
  vertical-align:-.1em; background:currentColor;
  -webkit-mask:center/contain no-repeat; mask:center/contain no-repeat; }

.status.success, .status.sent, .status.passed, .status.active { color:var(--good-ink); }
.status.success::before, .status.sent::before, .status.passed::before {
  mask-image:url("/assets/status-check-3acf178b.svg"); -webkit-mask-image:url("/assets/status-check-3acf178b.svg"); }

.status.failed, .status.cancelled { color:var(--bad-ink); }
/* A failed check of warning severity: the alert glyph, because it did fail, in
   amber, because it did not break the run. A clock would say it is still going. */
.status.soft-failed { color:var(--attn-ink); }
.status.failed::before, .status.cancelled::before, .status.soft-failed::before {
  mask-image:url("/assets/status-alert-21cd8fcb.svg"); -webkit-mask-image:url("/assets/status-alert-21cd8fcb.svg"); }

/* Waiting on a person is the state an operator is scanning for, so it is the
   one that gets amber. Merely in flight is blue and wants nothing. */
.status.waiting, .status.awaiting_approval, .status.paused,
.status.missing { color:var(--attn-ink); }
.status.waiting::before, .status.paused::before, .status.needs-person::before,
.status.missing::before {
  mask-image:url("/assets/status-clock-f896aabf.svg"); -webkit-mask-image:url("/assets/status-clock-f896aabf.svg"); }

.status.running, .status.queued, .status.pending { color:var(--live-ink); }
.status.running::before, .status.queued::before, .status.pending::before {
  mask-image:url("/assets/status-refresh-c282ce6e.svg"); -webkit-mask-image:url("/assets/status-refresh-c282ce6e.svg"); }
/* Last, so it beats .status.pending: a task waiting on a click is amber, not
   the blue of one that is simply in flight. */
.status.needs-person { color:var(--attn-ink); }
.status.needs-person::before { mask-image:url("/assets/status-clock-f896aabf.svg");
  -webkit-mask-image:url("/assets/status-clock-f896aabf.svg"); }

.status.skipped, .status.draft, .status.closed { color:var(--muted); }

/* One card per task. The left edge carries the status, so a run's state is
   readable down the margin without reading a word. */
.task { border:1px solid var(--line); border-left-width:3px;
  border-radius:var(--radius-md); padding:.55rem .75rem; margin-bottom:.5rem; }
.task.success { border-left-color:var(--good-edge); }
.task.skipped { border-left-color:var(--line); }
.task.failed  { border-color:color-mix(in srgb,var(--bad-edge) 35%,var(--line));
  border-left-color:var(--bad-edge); }
.task.running, .task.pending, .task.queued { border-left-color:var(--live-edge); }
.task.needs-person { border-left-color:var(--attn-edge); }
.task-head { display:flex; align-items:center; gap:.55rem; flex-wrap:wrap; }
.task-head .facts { margin-left:auto; display:flex; gap:.75rem; flex-wrap:wrap;
  color:var(--muted); font-size:var(--text-xs); }
.task-head .btn { font-size:var(--text-xs); padding:.25rem .8rem; }
.task .err, .task .muted { margin:.35rem 0 0; }

table.runs { width:100%; border-collapse:collapse; font-size:var(--text-sm);
  margin-bottom:1rem; }
table.runs th { text-align:left; font-size:var(--text-xs); text-transform:uppercase;
  letter-spacing:var(--tracking-wide); color:var(--muted);
  border-bottom:1px solid var(--line); padding:.4rem .5rem;
  font-weight:var(--weight-semibold); }
table.runs td { padding:.45rem .5rem; border-bottom:1px solid var(--line); vertical-align:top; }
.err { color:var(--err); font-size:var(--text-sm); }

/* Configuration grid: many columns, so the setting name stays put while the
   clients scroll under it. */
.matrix-scroll { overflow-x:auto; }
table.matrix th { text-align:center; }
table.matrix th.rowhead { text-align:left; }
table.matrix td.cell { text-align:center; vertical-align:middle; white-space:nowrap; }
table.matrix td.cell input[type=text], table.matrix td.cell input[type=number] {
  width:7rem; text-align:center; }
table.matrix input[type=checkbox] { accent-color:var(--accent); width:1.05rem; height:1.05rem; }
table.matrix td.cell input:disabled { opacity:var(--opacity-disabled); cursor:not-allowed; }

/* A classification spanning its rows. Static, and it turns off the sticky name
   column: two sticky cells at left:0 would sit on top of each other. */
table.matrix td.classcol, table.matrix th.classcol { font-size:var(--text-xs);
  font-weight:var(--weight-semibold); text-transform:uppercase;
  letter-spacing:var(--tracking-wide); color:var(--muted);
  vertical-align:middle; border-right:1px solid var(--line); white-space:nowrap;
  padding:.45rem .7rem; }
table.matrix.has-classcol .rowhead { position:static; }

/* Addresses sit with the message they are for, one line per client, so each field
   has the width an address needs. */
table.addresses { table-layout:fixed; }
table.addresses th:first-child, table.addresses td:first-child { width:9rem; }
table.addresses .rowhead { position:static; font-size:var(--text-sm); }
table.addresses input { width:100%; text-align:left; }

/* What a message will say, read-only: label above value, not a form. */
dl.wording { display:grid; grid-template-columns:max-content 1fr; gap:.35rem 1rem;
  align-items:baseline; margin:0 0 1rem; font-size:var(--text-sm); }
dl.wording dt { color:var(--muted); font-size:var(--text-xs); text-transform:uppercase;
  letter-spacing:var(--tracking-wide); }
dl.wording dd { margin:0; min-width:0; }

/* Wording a client reads. Monospace because placeholders are literal. Only the
   textarea gets a box — a bordered block reads as a field you can type in. */
textarea.template-body { width:100%; font-family:var(--font-mono);
  font-size:var(--text-sm); line-height:var(--leading-normal); padding:.6rem .7rem;
  border:1px solid var(--border-field); border-radius:var(--radius-field);
  background:var(--card); color:var(--fg); }

/* Text you are shown, not text you fill in. */
pre.quoted, blockquote.quoted { margin:.1rem 0 .4rem; padding:.1rem 0 .1rem .9rem;
  border-left:2px solid var(--line); color:var(--muted); font-size:var(--text-sm);
  line-height:var(--leading-normal); background:none; }
pre.quoted { white-space:pre-wrap; font-family:var(--font-mono); }

/* A disclosure styled as a button, so editing is deliberate and needs no JS. */
details.reveal { margin:.2rem 0 .6rem; }
details.reveal > summary { display:inline-block; cursor:pointer;
  font-size:var(--text-xs); padding:.3rem .7rem; border:1px solid var(--line);
  border-radius:var(--radius-button); background:var(--card);
  box-shadow:var(--shadow-sm); color:var(--fg); list-style:none;
  transition:opacity var(--duration-fast) var(--ease-standard); }
details.reveal > summary::-webkit-details-marker { display:none; }
details.reveal > summary:hover { opacity:var(--hover-mix); }
/* Save sits beside Modify while open, and the field starts on its own line. */
details.reveal > input[type=submit] { margin-left:.4rem; font-size:var(--text-xs);
  padding:.3rem .8rem; }
details.reveal > textarea { display:block; margin-top:.5rem; }

/* Groups within one block — params from deliverables from checks. A ruled caption
   rather than a badge: it separates without competing with the row labels. */
table.matrix tr.group td { border-bottom:none; padding:1rem .5rem .2rem; }
table.matrix tr.group .rowhead { position:static; font-size:var(--text-xs);
  font-weight:var(--weight-semibold); text-transform:uppercase;
  letter-spacing:var(--tracking-wide); color:var(--muted);
  border-top:1px solid var(--line); }
table.matrix tr.group:first-child .rowhead { border-top:none; padding-top:.2rem; }

/* Produce and send read as one control per client, so they sit together. */
.pair { display:inline-flex; align-items:center; gap:.4rem; }
.pipe { color:var(--line); }
table.matrix .rowhead { position:sticky; left:0; background:var(--card);
  min-width:16rem; white-space:nowrap; }
table.matrix th:first-child { z-index:1; }
table.matrix .na { font-size:var(--text-xs); }

ul.list, ol.list { margin:0; padding-left:1.1rem; }
ul.list:has(> li > .period) { list-style:none; padding-left:0; }
ul.list li, ol.list li { margin:.25rem 0; }

.row { display:flex; flex-wrap:wrap; gap:.75rem; align-items:flex-end; }
.row label { display:flex; flex-direction:column; gap:.2rem; font-size:var(--text-xs);
  color:var(--muted); }
.row label.check { flex-direction:row; align-items:center; gap:.35rem; }

/* Fields follow the brand's form treatment: a neutral-300 hairline, and on focus
   the border goes to the foreground with a 4px 5% halo — no coloured ring. */
input, select, textarea { font:inherit; font-size:var(--text-sm);
  padding:.35rem .5rem; border:1px solid var(--border-field);
  border-radius:var(--radius-field); background:var(--card); color:var(--fg); }
input:focus, select:focus, textarea:focus { outline:none; border-color:var(--fg);
  box-shadow:var(--ring-field); }

/* Buttons are 4px, never the 8px --radius, and darken to 90% on hover. */
.btn, input[type=submit] { font:inherit; font-size:var(--text-sm);
  padding:.4rem .9rem; border-radius:var(--radius-button);
  border:1px solid var(--accent); background:var(--accent); color:var(--on-accent);
  cursor:pointer; transition:opacity var(--duration-fast) var(--ease-standard); }
.btn:hover, input[type=submit]:hover { opacity:var(--hover-mix); text-decoration:none; }
/* Loud only once you reach for it: a config page is mostly Remove buttons and
   they should not be the first thing the eye lands on. */
.btn.danger { background:transparent; color:var(--muted); border-color:var(--line); }
.btn.danger:hover, .btn.danger:focus-visible { opacity:1;
  color:var(--bad-ink); border-color:var(--bad-edge); background:var(--bad-wash); }
.btn.ghost { background:transparent; color:var(--accent); box-shadow:var(--shadow-sm); }
a.btn { text-decoration:none; display:inline-block; }
.linkish { background:none; border:none; color:var(--accent); cursor:pointer;
  font:inherit; font-size:var(--text-sm); padding:0; text-decoration:underline;
  text-underline-offset:4px; }
form { display:inline; }

.logs { font-family:var(--font-mono); font-size:var(--text-xs); line-height:1.6;
  max-height:22rem; overflow-y:auto; background:var(--card);
  border:1px solid var(--line); border-radius:var(--radius-md); padding:.6rem; }
.logline { display:flex; gap:.6rem; }
.logline .ts { color:var(--muted); flex:0 0 auto; }
.logline.error .msg { color:var(--bad-ink); }
.logline.warning .msg { color:var(--attn-ink); }
.runhead { display:flex; align-items:center; gap:.75rem; margin:.5rem 0 1rem; }

.crumbs { display:flex; flex-wrap:wrap; align-items:center; gap:.4rem;
  font-size:var(--text-sm); color:var(--muted); margin:0 0 .75rem; }
.crumbs .sep { color:var(--line); }
.crumbs [aria-current] { color:var(--fg); font-weight:var(--weight-medium); }

.years { display:flex; flex-wrap:wrap; gap:.4rem; margin:0 0 1rem; }
.years a { padding:.2rem .55rem; border:1px solid var(--line);
  border-radius:var(--radius-full); font-size:var(--text-sm); color:var(--muted);
  text-decoration:none; }
.years a:hover { color:var(--fg); border-color:var(--fg); text-decoration:none; }
.years [aria-current] { color:var(--fg); border-color:var(--fg);
  font-weight:var(--weight-medium); }

.year { margin-bottom:1.25rem; }
.year h3 { margin:.25rem 0 .5rem; font-size:var(--text-xs); color:var(--muted);
  text-transform:uppercase; letter-spacing:var(--tracking-wide); }
.period-grid { display:grid; grid-template-columns:repeat(var(--cols),1fr); gap:.5rem; }
/* Past a dozen columns a cell is too narrow for words, so it keeps the label and
   lets the stripe say the rest. The full state stays in the title attribute. */
.period-grid.dense { gap:.25rem; }
.period-row { display:flex; align-items:center; gap:.5rem; margin-bottom:.25rem; }
.period-row .month { flex:0 0 2.2rem; font-size:var(--text-xs); color:var(--muted);
  text-transform:uppercase; letter-spacing:var(--tracking-wide); text-align:right; }
.period-row .period-grid { flex:1 1 auto; }
.period-grid.dense .period { padding:.3rem .15rem; align-items:center;
  border-left-width:1px; border-bottom-width:3px; }
.period-grid.dense .period .label { font-size:var(--text-xs); }
.period-grid.dense .period .status, .period-grid.dense .period .muted { display:none; }
.period-grid.dense .period.success { border-bottom-color:var(--good-edge); }
.period-grid.dense .period.failed { border-bottom-color:var(--bad-edge); }
.period-grid.dense .period.missing { border-bottom-color:var(--attn-edge); }
.period-grid.dense .period.running, .period-grid.dense .period.queued {
  border-bottom-color:var(--live-edge); }
.period { display:flex; flex-direction:column; gap:.15rem; padding:.5rem .6rem;
  border:1px solid var(--line); border-left-width:3px; border-radius:var(--radius-md);
  text-decoration:none; color:var(--fg); background:var(--card); }
.period:hover { border-color:var(--accent); text-decoration:none; }
.period .label { font-weight:var(--weight-semibold); font-size:var(--text-sm); }
.period.success { border-left-color:var(--good-edge); }
.period.failed { border-left-color:var(--bad-edge); }
.period.missing { border-left-color:var(--attn-edge); }
.period.running, .period.queued { border-left-color:var(--live-edge); }
.period.waiting { border-left-color:var(--attn-edge); }
.period.future { opacity:var(--opacity-disabled); }

/* An input is a name and a state; what it is and how to send it live in its
   dialog, so the row stays one line and the action column stays narrow. */
table.inputs td.actions { text-align:right; white-space:nowrap; width:1%; }
table.inputs .btn { font-size:var(--text-xs); padding:.25rem .8rem; }
/* The description on hover, half a second in, so moving across the table does
   not flash a tooltip on every row. Delay on the way in only — leaving is
   immediate, and no delay at all reads as a twitch. */
.hint-wrap { position:relative; display:inline-block; }
.hint { display:inline-flex; align-items:center; justify-content:center;
  width:1.2rem; height:1.2rem; border:1px solid var(--line);
  border-radius:var(--radius-full); cursor:help; color:var(--muted);
  font-size:var(--text-xs); line-height:1; vertical-align:middle; user-select:none; }
.hint-wrap:hover .hint, .hint:focus-visible { border-color:var(--accent); color:var(--accent); }
.tip { position:absolute; left:0; top:calc(100% + .35rem); z-index:20;
  width:max-content; max-width:min(24rem,60vw); padding:.5rem .65rem;
  border-radius:var(--radius-md); background:var(--fg); color:var(--card);
  font-size:var(--text-xs); line-height:1.45;
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity var(--duration-instant) var(--ease-standard),
             visibility 0s linear var(--duration-instant); }
.hint-wrap:hover .tip, .hint:focus-visible + .tip {
  opacity:1; visibility:visible; transition-delay:.5s, .5s; }

/* The dialog lives in the row it belongs to, so it inherits that action cell's
   right-aligned, nowrap text and has to undo both. Filenames are long and have no
   break opportunity; they wrap or they spill. */
dialog.upload-dialog { width:min(34rem,92vw); border:1px solid var(--line);
  border-radius:var(--radius-card); background:var(--card); color:var(--fg);
  padding:1.25rem; text-align:left; white-space:normal; overflow-wrap:anywhere; }
dialog.upload-dialog::backdrop { background:rgb(0 0 0 / .45); }
dialog.upload-dialog dl.wording dd { overflow-wrap:anywhere; }
.dialog-head { display:flex; align-items:baseline; justify-content:space-between;
  gap:1rem; margin-bottom:.4rem; }
.dialog-head h2 { margin:0; }
.dialog-form { display:block; }

/* The whole zone is a label, so clicking opens the picker with no JavaScript;
   Stimulus only adds the drop. */
label.dropzone { display:flex; flex-direction:column; align-items:center; gap:.3rem;
  padding:1.6rem 1rem; margin:.5rem 0 .8rem; text-align:center; cursor:pointer;
  border:1px dashed var(--border-field); border-radius:var(--radius-md);
  background:var(--bg); color:var(--muted); font-size:var(--text-sm); }
label.dropzone:hover, label.dropzone.over { border-color:var(--accent); color:var(--accent); }
label.dropzone strong { font-family:var(--font-mono); font-size:var(--text-xs);
  color:var(--fg); }
.hidden-field { display:none; }
input[type=file] { padding:.25rem .4rem; max-width:22rem; }
input[type=file]::file-selector-button { font:inherit; font-size:var(--text-xs);
  margin-right:.6rem; padding:.25rem .7rem; border:1px solid var(--line);
  border-radius:var(--radius-button); background:var(--bg); color:var(--fg);
  cursor:pointer; }
input[type=file]::file-selector-button:hover { border-color:var(--accent);
  color:var(--accent); }
input[type=submit]:disabled { opacity:var(--opacity-disabled); cursor:not-allowed; }

/* A token is long and must be copied whole, so it wraps rather than overflows. */
.linkbox { display:flex; align-items:center; gap:.6rem; flex-wrap:wrap; }
.linkbox code { word-break:break-all; }

/* The brand's one card is the contact form: white, 16px, stacked fields. Sign-in
   and the client upload page are the only places ProjectHub has the same shape. */
.auth-card { max-width:26rem; margin:var(--space-16) auto; background:var(--card);
  border:1px solid var(--line); border-radius:var(--radius-card);
  padding:var(--space-8); }
.auth-card h1 { margin:0 0 var(--space-6); }
.auth-card .field { display:flex; flex-direction:column; gap:.3rem;
  margin-bottom:var(--space-4); }
.auth-card .field label { font-size:var(--text-xs); color:var(--muted);
  text-transform:uppercase; letter-spacing:var(--tracking-wide); }
.auth-card input[type=email], .auth-card input[type=password] { width:100%; padding:.5rem .7rem; }
.auth-card form { display:block; }
.auth-card input[type=submit] { width:100%; margin-top:var(--space-2); }
.auth-card .after { margin:var(--space-4) 0 0; font-size:var(--text-sm); }

.client-page main { max-width:48rem; }
