/* ─────────────────────────────────────────────────────────────────────────
   Donellus — design tokens
   Carried forward from the Fieldbook desktop build so the two feel like one
   product. The colours below are the DAY values; sky.js interpolates them
   toward night by the real Phoenix sun elevation.
   ───────────────────────────────────────────────────────────────────────── */

/* The hidden attribute means hidden. It is only a default of display:none, so
   any rule that sets `display` on an element beats it — and this has now cost
   two bugs: a chip rail that stayed painted over the text it had handed back,
   and an empty orange pill sitting on the placeholder because the slot chip
   was given display:inline-block when the bar learned to wrap. Both were
   invisible in the DOM, which said hidden, and plain on screen.

   Stated once, with !important, because the whole point is that nothing later
   should be able to override it. */
[hidden] { display: none !important; }

/* Registered, so it can be transitioned. A plain custom property inside a
   keyframe is substituted rather than interpolated — change it and every
   keyframe's value is recomputed on the spot, so the photograph snaps to a
   new position mid-drift. Registering it as a number makes it animatable, and
   the transition below turns "the wind is now stronger" from a jump into the
   wind getting up over about a second. */
@property --leaf-amp {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

:root {
  transition: --leaf-amp .9s cubic-bezier(.33,.68,.36,1);
  --wx-warm: 255, 150, 62;
  --wx-sun: .34;
  --wx-cool: .27;
  --wx-period: 154s;
  --wx-drift: 37s;
  --leaf-amp: 1;
}


/* BEFORE A PUBLISHED BUILD — see docs/pre-launch.md §2.
   This is a hotlink to Google, and it is a placeholder in two ways. On a LAN
   with no internet the request fails and the wordmark falls back to Georgia
   without saying so. And neither family has been licence-checked for
   redistribution — OFL is permissive but the terms still have to be met and
   the licence file shipped. Self-host the woff2 into public/fonts/ and drop
   this line. Castoro Titling is temporary; whatever replaces it needs the same
   two questions asked. */
@import url('https://fonts.googleapis.com/css2?family=Castoro+Titling&family=Figtree:wght@400;600;700&display=swap');

:root {
  /* Light and clean throughout. Status chips keep their colour below, because
     that is the one place colour carries meaning rather than decoration. */
  --color-bg: #ffffff;
  --color-surface: #fafafb;
  --color-text: #1c1e21;
  --color-accent: #1c1e21;
  --color-accent-2: #7a8a5e;
  --color-divider: #e6e7ea;
  --brand: #1c1e21;

  --color-neutral-100: #ffffff;
  --color-neutral-200: #f1f2f4;
  --color-neutral-300: #e3e5e8;
  --color-neutral-400: #c9ccd1;
  --color-neutral-500: #a6abb2;
  --color-neutral-600: #7b8087;
  --color-neutral-700: #5c6169;
  --color-neutral-800: #3a3e44;
  --color-neutral-900: #1c1e21;

  --color-accent-100: #fff2eb;
  --color-accent-200: #ffe1d0;
  --color-accent-300: #ffc6a5;
  --color-accent-400: #f6a06b;
  --color-accent-500: #d67f48;
  --color-accent-600: #34383e;
  --color-accent-orange-600: #b2622d;
  --color-accent-700: #8c491a;
  --color-accent-800: #643312;
  --color-accent-900: #402310;

  --color-accent-2-100: #f0fae1;
  --color-accent-2-200: #e1eecc;
  --color-accent-2-300: #ccdbb2;
  --color-accent-2-400: #aebf92;
  --color-accent-2-500: #8fa073;
  --color-accent-2-600: #728157;
  --color-accent-2-700: #56633f;
  --color-accent-2-800: #3d472b;
  --color-accent-2-900: #272e1b;

  /* litigation / locked / destructive — new in Donellus */
  --color-clay-100: #fdeceb;
  --color-clay-200: #f8d3d0;
  --color-clay-700: #8f3a33;
  --color-clay-800: #6b2a25;

  /* The wordmark is Castoro Titling — a high-contrast display serif, and the
     one moment of typographic character in an interface that is otherwise
     Figtree at weight. */
  --font-display: "Castoro Titling", Georgia, "Times New Roman", serif;
  --font-heading: "Figtree", system-ui, -apple-system, sans-serif;
  --font-heading-weight: 700;
  --font-body: "Figtree", system-ui, -apple-system, sans-serif;

  --space-1: 4.4px;  --space-2: 8.8px;  --space-3: 13.2px;
  --space-4: 17.6px; --space-6: 26.4px; --space-8: 35.2px;

  --radius-sm: 8px; --radius-md: 16px; --radius-lg: 28px;
  --shadow-sm: 0 1px 2px color-mix(in srgb, #2e2b25 14%, transparent);
  --shadow-md: 0 3px 10px color-mix(in srgb, #2e2b25 16%, transparent);
  --shadow-lg: 0 12px 32px color-mix(in srgb, #2e2b25 22%, transparent);
}

*, *::before, *::after { box-sizing: border-box; }
body {
  margin: 0; font-size: 15px; line-height: 1.55;
  background: var(--color-bg); color: var(--color-text);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}
h1,h2,h3,h4 {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  line-height: 1.12; letter-spacing: -0.015em; margin: 0 0 var(--space-2);
}
p { margin: 0 0 var(--space-3); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

/* ── buttons ─────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; text-decoration: none;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 14px; line-height: 1.2; color: var(--color-text);
  background: transparent; border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: 999px; transition: background .14s, border-color .14s;
}
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn-primary { background: var(--color-accent); color: #fbeede; }
.btn-primary:hover:not(:disabled) { background: var(--color-accent-600); }
.btn-primary:active:not(:disabled) { background: var(--color-accent-700); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover:not(:disabled) { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-2); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.btn-sm { font-size: 12.5px; padding: 5px var(--space-3); }
.btn-block { width: 100%; }

/* ── form controls ───────────────────────────────────────────────────── */
.field > label {
  display: block; font-size: 12px; margin-bottom: 5px;
  color: color-mix(in srgb, var(--color-text) 70%, transparent);
}
.input, .select {
  width: 100%; min-height: 36px; padding: 6px 14px; font: inherit; font-size: 14px;
  color: var(--color-text); caret-color: var(--color-accent);
  background: var(--color-surface);
  border: 1px solid var(--color-divider); border-radius: 999px;
}
.select { appearance: none; cursor: pointer; padding-right: 32px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 17px) 15px, calc(100% - 12px) 15px;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
}
.input:hover, .select:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible, .select:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
textarea.input { min-height: 84px; resize: vertical; border-radius: var(--radius-md); line-height: 1.5; }
.input:disabled, .select:disabled, textarea.input:disabled { opacity: .55; cursor: not-allowed; }

/* ── tags ────────────────────────────────────────────────────────────── */
.tag {
  display: inline-flex; align-items: center; white-space: nowrap;
  font-size: 11px; letter-spacing: .02em; padding: 3px 10px;
  border-radius: calc(var(--radius-md) * .75);
  background: var(--color-neutral-200); color: var(--color-neutral-700);
}
.tag-intake            { background: var(--color-neutral-200);   color: var(--color-neutral-800); }
.tag-treating          { background: var(--color-accent-2-100);  color: var(--color-accent-2-800); }
.tag-records-requested { background: var(--color-accent-100);    color: var(--color-accent-800); }
.tag-records-received  { background: var(--color-accent-200);    color: var(--color-accent-800); }
.tag-demand-sent       { background: var(--color-accent-300);    color: var(--color-accent-900); }
.tag-negotiating       { background: var(--color-accent-400);    color: var(--color-accent-900); }
.tag-settled           { background: var(--color-accent-2-300);  color: var(--color-accent-2-900); }
.tag-litigation        { background: var(--color-clay-200);      color: var(--color-clay-800); }
.tag-closed            { background: var(--color-neutral-300);   color: var(--color-neutral-800); }

/* ── app shell ───────────────────────────────────────────────────────── */
.shell { display: flex; height: 100vh; overflow: hidden; }
.sidebar {
  width: 232px; flex-shrink: 0; overflow-y: auto; background: var(--color-neutral-100);
  padding: var(--space-6) var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-6);
  border-right: 1px solid var(--color-neutral-200);
}
.wordmark {
  text-align: left; border: none; background: transparent; cursor: pointer;
  font-family: var(--font-display); font-size: 24px; color: var(--brand);
  padding: 0 0 0 var(--space-2);
}
.wordmark small { display: block; font-family: var(--font-body); font-size: 11px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--color-neutral-600); margin-top: 2px; }
.nav { display: flex; flex-direction: column; gap: var(--space-1); }
.nav-item {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3); border: none; background: transparent;
  border-radius: 999px; cursor: pointer; font: inherit; font-size: 14px;
  color: var(--color-neutral-800); text-align: left;
}
.nav-item:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.nav-item.active { background: var(--color-accent); color: #fbeede; font-weight: 600; }
.sidebar-foot { margin-top: auto; display: flex; flex-direction: column; gap: var(--space-2); }
.userchip {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2); border-radius: var(--radius-md);
  background: var(--color-neutral-200); font-size: 13px;
}
.avatar {
  width: 30px; height: 30px; flex-shrink: 0; border-radius: 999px;
  display: grid; place-items: center; font-family: var(--font-heading);
  font-size: 13px; background: var(--color-accent); color: #fbeede;
}
.userchip-role { font-size: 11px; color: var(--color-neutral-600); }

.main { flex: 1; display: flex; flex-direction: column; min-width: 0; overflow: hidden; }
.banner-wrap { padding: var(--space-4) var(--space-8) 0; }
.banner-frame { overflow: hidden; border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); position: relative; }
/* The scene is drawn at a fixed 1152x352; on wide screens that would eat a
   third of the viewport, so cap the height and let it crop rather than squash. */
.banner-frame canvas {
  width: 100%; aspect-ratio: 1152/352; max-height: 210px;
  display: block; object-fit: cover; object-position: 50% 42%;
}
.banner-frame::after {
  content: ''; position: absolute; inset: auto 0 0 0; height: 52%;
  background: linear-gradient(to top, rgba(18,8,26,.72), rgba(18,8,26,.28) 45%, transparent);
  pointer-events: none;
}
.banner-overlay {
  position: absolute; left: var(--space-6); right: var(--space-6); bottom: var(--space-4);
  color: #fff5e4; text-shadow: 0 2px 10px rgba(20,10,30,.55); z-index: 1;
}
.banner-overlay h1 { font-size: clamp(24px, 3vw, 34px); margin: 0; color: inherit; }
.banner-overlay p { margin: 2px 0 0; font-size: 13.5px; opacity: .92; }
.content { flex: 1; padding: var(--space-6) var(--space-8) var(--space-8); overflow: auto; }

/* ── page furniture ──────────────────────────────────────────────────── */
.toolbar {
  display: flex; align-items: center; gap: var(--space-3);
  flex-wrap: wrap; margin-bottom: var(--space-6);
}
.toolbar .grow { flex: 1; min-width: 180px; }
.section-title { font-size: 19px; margin-bottom: var(--space-3); }

/* ── narrowing the record ─────────────────────────────────────────────────
   A view control, so it reads as chrome rather than as part of the record:
   small labels above small selects, sitting between the heading and the
   entries it governs. */
.tlfilter {
  display: flex; align-items: flex-end; gap: var(--space-3);
  flex-wrap: wrap; margin-bottom: var(--space-3);
}
.tlfilter .spacer { flex: 1 1 auto; }
.tlf-field { display: flex; flex-direction: column; gap: 4px; }
.tlf-field > span {
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--color-text-dim);
}
.tlf-field .select { min-width: 168px; padding-top: 7px; padding-bottom: 7px; font-size: 14px; }
.tlf-count {
  font-size: 13px; color: var(--color-text-dim); margin-bottom: var(--space-3);
}
.muted { color: var(--color-neutral-600); }
.empty {
  padding: var(--space-8); text-align: center; color: var(--color-neutral-600);
  background: var(--color-neutral-100); border-radius: var(--radius-lg);
  border: 1px dashed var(--color-neutral-300);
}

/* ── client cards ────────────────────────────────────────────────────── */
.card-grid {
  display: grid; gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  align-items: start;   /* an open composer must not stretch its whole row */
}
.client-card {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-4); border-radius: calc(var(--radius-lg) * 1.15);
  background: var(--color-surface); box-shadow: var(--shadow-sm);
  border: 1px solid transparent; cursor: pointer; text-align: left;
  font: inherit; color: inherit;
  transition: transform .16s cubic-bezier(.2,.7,.2,1), box-shadow .16s, border-color .16s;
}
.client-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: var(--color-accent-300); }
.client-card:active { transform: translateY(-1px); }
.client-card .kicker {
  font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--color-accent); font-weight: 600;
}
.client-card .name { font-family: var(--font-heading); font-size: 18px; line-height: 1.2; }
.client-card .last {
  font-size: 13px; color: var(--color-neutral-700); margin: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.card-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-2); margin-top: auto; padding-top: var(--space-2);
  border-top: 1px solid color-mix(in srgb, var(--color-text) 9%, transparent);
}
.count-dot {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--color-neutral-700);
}
.count-dot b {
  display: grid; place-items: center; min-width: 19px; height: 19px; padding: 0 5px;
  border-radius: 999px; background: var(--color-accent-2-200);
  color: var(--color-accent-2-800); font-size: 11px; font-weight: 700;
}
.count-dot.zero b { background: var(--color-neutral-200); color: var(--color-neutral-600); }

/* ── client detail + composer ────────────────────────────────────────── */
.detail-head {
  display: flex; align-items: flex-start; gap: var(--space-4);
  margin-bottom: var(--space-6); flex-wrap: wrap;
}
.detail-head h2 { font-size: 26px; margin: 0; }
.panel {
  background: var(--color-surface); border-radius: calc(var(--radius-lg) * 1.15);
  padding: var(--space-4); box-shadow: var(--shadow-sm);
}
.composer { margin-bottom: var(--space-6); }
.composer-row {
  display: flex; align-items: center; gap: var(--space-3);
  margin-top: var(--space-3); flex-wrap: wrap;
}
.composer-row .spacer { flex: 1; }
.hint { font-size: 11.5px; color: var(--color-neutral-600); margin: var(--space-2) 0 0; }
.hint b { color: var(--color-clay-700); font-weight: 600; }
.kbd {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;
  padding: 1px 5px; border-radius: 5px; background: var(--color-neutral-200);
  border: 1px solid var(--color-neutral-300); color: var(--color-neutral-700);
}

/* ── timeline ────────────────────────────────────────────────────────── */
.timeline { position: relative; padding-left: var(--space-6); }
.timeline::before {
  content: ''; position: absolute; left: 5px; top: 6px; bottom: 6px;
  width: 2px; background: var(--color-neutral-300); border-radius: 2px;
}
.day-group { margin-bottom: var(--space-6); }
.day-label {
  font-family: var(--font-heading); font-size: 13px; letter-spacing: .04em;
  color: var(--color-neutral-700); margin-bottom: var(--space-3);
  display: flex; align-items: center; gap: var(--space-2);
}
.entry { position: relative; margin-bottom: var(--space-3); }
.entry::before {
  content: ''; position: absolute; left: calc(var(--space-6) * -1 + 1px); top: 15px;
  width: 10px; height: 10px; border-radius: 999px;
  background: var(--color-accent); box-shadow: 0 0 0 3px var(--color-bg);
}
.entry.is-correction::before { background: var(--color-clay-700); }
.entry-body {
  background: var(--color-surface); border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4); box-shadow: var(--shadow-sm);
}
.entry-meta {
  display: flex; align-items: center; gap: var(--space-2);
  flex-wrap: wrap; font-size: 11.5px; color: var(--color-neutral-600);
  margin-bottom: 5px;
}
.entry-meta time { font-variant-numeric: tabular-nums; }
.entry-meta .who { font-weight: 600; color: var(--color-neutral-800); }
.entry-note { margin: 0; font-size: 14.5px; white-space: pre-wrap; }
.entry-actions { margin-top: var(--space-2); display: flex; gap: var(--space-2); }
.correction-banner {
  font-size: 11.5px; color: var(--color-clay-700);
  background: var(--color-clay-100); border-radius: var(--radius-sm);
  padding: 4px var(--space-2); margin-bottom: var(--space-2);
  display: inline-flex; align-items: center; gap: 5px;
}
.superseded .entry-note { opacity: .62; }

/* ── locked / submitted state ────────────────────────────────────────── */
.lockbar {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-md);
  background: var(--color-accent-2-100); color: var(--color-accent-2-800);
  font-size: 13.5px; margin-bottom: var(--space-4);
}
.lockbar.auto { background: var(--color-neutral-200); color: var(--color-neutral-800); }
.lock-pill {
  display: inline-flex; align-items: center; gap: 4px; font-size: 11px;
  padding: 2px 9px; border-radius: 999px;
  background: var(--color-neutral-300); color: var(--color-neutral-800);
}

/* ── manager table ───────────────────────────────────────────────────── */
.table-wrap {
  background: var(--color-surface); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm); overflow: auto;
}
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
  text-align: left; font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--color-divider);
  position: sticky; top: 0; background: var(--color-surface); white-space: nowrap;
}
.table td {
  padding: var(--space-3) var(--space-4); vertical-align: top;
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}
.table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }
.table .t-time { font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--color-neutral-700); }
.table .t-note { min-width: 300px; }
.table .t-client, .table .t-who { white-space: nowrap; }
.linkish {
  background: none; border: none; padding: 0; font: inherit; cursor: pointer;
  color: var(--color-accent-700); font-weight: 600; text-align: left;
}
.linkish:hover { text-decoration: underline; }
.statbar { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-6); }
.stat {
  background: var(--color-neutral-100); border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4); min-width: 118px;
  border: 1px solid var(--color-neutral-200);
}
.stat .n { font-family: var(--font-heading); font-size: 25px; line-height: 1; color: var(--brand); }
.stat .l { font-size: 11px; letter-spacing: .07em; text-transform: uppercase; color: var(--color-neutral-600); margin-top: 5px; }

/* ── login ───────────────────────────────────────────────────────────── */
.login {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--space-6); padding: var(--space-8);
  background: radial-gradient(120% 80% at 50% -10%, #ffffff 0%, #fdfdfe 45%, #f6f6f8 100%);
}
.login-inner {
  width: min(880px, 94vw); display: flex; flex-direction: column;
  align-items: center; gap: var(--space-6);
  animation: riseIn .55s cubic-bezier(.2,.7,.2,1) both;
}
@keyframes riseIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
.login-banner { width: 100%; border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-md); }
.login-banner canvas { width: 100%; aspect-ratio: 1152/352; display: block; }
.login-title { font-family: var(--font-display); font-size: clamp(42px, 7vw, 62px); color: var(--brand); margin: 0; line-height: 1; }
.login-tag { font-size: 17px; color: var(--color-neutral-700); margin: var(--space-2) 0 0; }
.login-card {
  width: min(380px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-6); border-radius: calc(var(--radius-lg) * 1.15);
  background: #ffffff; border: 1px solid #ececef; box-shadow: 0 6px 24px rgba(28,30,33,.08);
}
.login-error {
  font-size: 13px; padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-clay-100); color: var(--color-clay-700);
}
.login-foot { font-size: 12px; color: var(--color-neutral-600); text-align: center; }

/* ── demo mode ───────────────────────────────────────────────────────── */
.demo-ribbon {
  padding: 7px var(--space-6); font-size: 12.5px; text-align: center;
  background: var(--color-accent-700); color: #fbeede; letter-spacing: .01em;
}
.shell.has-ribbon { height: calc(100vh - 30px); }

.or-rule {
  display: flex; align-items: center; gap: var(--space-2);
  margin: var(--space-2) 0 0; font-size: 11.5px; color: var(--color-neutral-600);
}
.or-rule::before, .or-rule::after {
  content: ''; flex: 1; height: 1px; background: var(--color-divider);
}

/* ── toast ───────────────────────────────────────────────────────────── */
.toast {
  position: fixed; bottom: var(--space-6); left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: var(--color-neutral-900); color: var(--color-neutral-100);
  padding: var(--space-2) var(--space-4); border-radius: 999px;
  font-size: 13px; box-shadow: var(--shadow-lg);
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s; z-index: 60;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ── dialog ──────────────────────────────────────────────────────────── */
.backdrop {
  position: fixed; inset: 0; display: grid; place-items: center;
  padding: var(--space-4); background: rgba(10,8,16,.55); z-index: 50;
}
.dialog {
  width: min(460px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-6); border-radius: calc(var(--radius-lg) * 1.15);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.dialog h3 { font-size: 21px; margin: 0; }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }

@media (max-width: 860px) {
  .shell { flex-direction: column; height: auto; overflow: visible; }
  .main { overflow: visible; }
  .content { overflow: visible; }
  .sidebar { width: 100%; flex-direction: row; align-items: center; gap: var(--space-4); padding: var(--space-3) var(--space-4); }
  .nav { flex-direction: row; }
  .sidebar-foot { margin-top: 0; margin-left: auto; }
  .banner-wrap { padding: var(--space-3) var(--space-4) 0; }
  .content { padding: var(--space-4); }
}

/* ═══════════════════════════════════════════════════════════════════════
   SEARCH HOME
   One search bar, nothing else. Autocomplete follows Baymard's patterns:
   at most 8 suggestions, the predictive part bolded rather than what was
   typed, arrow keys that loop, the highlighted suggestion copied into the
   field, and recents shown before anything is typed.
   ═══════════════════════════════════════════════════════════════════════ */






.bigsearch input {
  width: 100%; font: inherit; font-size: 17px;
  padding: 15px var(--space-6); border-radius: 999px;
  background: var(--color-surface); color: var(--color-text);
  border: 1px solid var(--color-divider); box-shadow: var(--shadow-sm);
  caret-color: var(--color-accent);
}
.bigsearch input:hover { box-shadow: var(--shadow-md); }
.bigsearch input:focus-visible { border-color: var(--color-accent); outline: none; box-shadow: var(--shadow-md); }
.bigsearch input::placeholder { color: var(--color-neutral-500); }

.acbox {
  position: absolute; left: 0; right: 0; top: calc(100% + 8px); z-index: 30;
  background: var(--color-surface); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg); padding: 6px; overflow: hidden;
}
.ac-head {
  font-size: 12px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--color-neutral-600); padding: 6px var(--space-4) 4px;
}
.ac-item {
  display: flex; align-items: center; gap: var(--space-3); width: 100%;
  border: none; background: transparent; cursor: pointer; text-align: left;
  font: inherit; font-size: 16.5px; color: var(--color-text);
  padding: 12px var(--space-4); border-radius: 999px;
}
.ac-item:hover, .ac-item.on { background: color-mix(in srgb, var(--color-accent) 14%, transparent); }
.ac-item .nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ac-item .nm b { font-weight: 700; }          /* the predictive part, per Baymard */
.ac-item .cn {
  font-size: 14px; color: var(--color-neutral-600);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.ac-empty { padding: 10px var(--space-4); font-size: 13.5px; color: var(--color-neutral-600); text-align: center; }



/* ── client page ─────────────────────────────────────────────────────── */
.client-head { margin-bottom: var(--space-6); }
.client-head h2 { font-size: 27px; margin: 0 0 4px; }
.client-head .meta {
  display: flex; align-items: center; gap: var(--space-2);
  flex-wrap: wrap; font-size: 13.5px; color: var(--color-neutral-600);
}
.typepick { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--space-3); }
.typebtn {
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  border: 1px solid var(--color-divider); background: transparent;
  border-radius: 999px; padding: 6px 12px; font: inherit; font-size: 12.5px;
  color: var(--color-neutral-800); white-space: nowrap;
}
.typebtn:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.typebtn.on { background: var(--color-accent); color: #fbeede; border-color: transparent; }
.type-icon {
  display: inline-grid; place-items: center; width: 21px; height: 21px; flex-shrink: 0;
  border-radius: 999px; background: var(--color-accent-100);
  color: var(--color-accent-800); font-size: 11.5px;
}

/* ── all clients ─────────────────────────────────────────────────────── */
.filters { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-4); }
.filters .grow { flex: 1; min-width: 200px; }
.rowlink {
  background: none; border: none; padding: 0; font: inherit; cursor: pointer;
  color: var(--color-accent-700); font-weight: 600; text-align: left;
}
.rowlink:hover { text-decoration: underline; }

/* ═══════════════════════════════════════════════════════════════════════
   ICON PICKER  (search screen only)
   Physical objects sitting on a surface, in the spirit of 51 Worldwide
   Games: a calm neutral tray, crisp type, and real 3D solids you can spin.

   The 3D is genuine CSS: each object is a stack of layers inside a
   preserve-3d parent tipped back on X, so translateZ lifts a layer off the
   table and rotateZ spins it about the vertical axis.
   ═══════════════════════════════════════════════════════════════════════ */

.picker {
  --tray: #eeece6;
  --tray-line: #d8d5cc;
  --ink: #23262b;
  --ink-soft: #6c7178;
  width: min(760px, 94vw);
  margin-top: var(--space-8);
  background: var(--tray);
  border-radius: 22px;
  padding: var(--space-4) var(--space-6) var(--space-6);
  box-shadow: 0 1px 0 rgba(255,255,255,.7) inset, 0 10px 30px rgba(35,38,43,.10);
}

/* the speech-bubble read-out above the row */
.picker-info {
  position: relative;
  background: #f7f6f2;
  border: 1.5px solid var(--tray-line);
  border-radius: 14px;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-6);
  min-height: 66px;
}
.picker-info::after {
  content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  border: 11px solid transparent; border-top-color: #f7f6f2; border-bottom: 0;
  filter: drop-shadow(0 1.5px 0 var(--tray-line));
}
.picker-info h3 {
  font-family: var(--font-body); font-weight: 700; letter-spacing: -0.01em;
  font-size: 19px; color: var(--ink); margin: 0 0 2px;
}
.picker-info p { font-size: 13.5px; color: var(--ink-soft); margin: 0; }

.picker-row {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--space-2);
  perspective: 900px; perspective-origin: 50% 30%;
}
.pick {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-2);
  background: none; border: none; cursor: pointer; font: inherit;
  padding: var(--space-2) 0 0; border-radius: 16px; color: var(--ink);
}
.pick:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }
.pick-label {
  font-size: 13px; font-weight: 600; letter-spacing: -0.005em;
  color: var(--ink-soft); transition: color .18s;
}
.pick-wrap:hover .pick-label, .pick:focus-visible .pick-label { color: var(--ink); }

/* selection arc, like the reference */
.pick-arc { height: 11px; width: 92px; position: relative; }
.pick-arc::after {
  content: ''; position: absolute; inset: 0;
  border-radius: 0 0 60px 60px;
  border: 2.5px solid var(--color-accent); border-top: 0;
  opacity: 0; transform: scaleX(.6); transition: opacity .2s, transform .2s;
}
.pick-wrap:hover .pick-arc::after, .pick:focus-visible .pick-arc::after { opacity: 1; transform: none; }

/* ── the stage each solid stands on ─────────────────────────────────── */
.stage {
  width: 152px; height: 134px; position: relative;
  display: grid; place-items: center;
  transform-style: preserve-3d;
}
.obj {
  position: relative; width: 64px; height: 64px;
  transform-style: preserve-3d;
  /* tipped back so discs read as lying on a table */
  transform: rotateX(62deg) rotateZ(0deg) scale(1.75);
  transition: transform .35s cubic-bezier(.2,.8,.25,1);
}
.pick-wrap:hover .obj, .pick:focus-visible .obj {
  animation: objspin 4.8s linear infinite;
}
@keyframes objspin {
  from { transform: rotateX(62deg) rotateZ(0deg)   scale(1.42); }
  to   { transform: rotateX(62deg) rotateZ(360deg) scale(1.42); }
}
/* a flat round layer — the building block for chips, dials and pawn bodies */
.disc {
  position: absolute; left: 50%; top: 50%; border-radius: 50%;
  transform: translate(-50%, -50%) translateZ(var(--z, 0px));
  width: var(--d, 40px); height: var(--d, 40px);
  background: var(--c, #c8c8c8);
  box-shadow: 0 1px 0 rgba(0,0,0,.16);
}
/* a flat rounded slab — sheets and cards */
.slab {
  position: absolute; left: 50%; top: 50%; border-radius: var(--r, 7px);
  transform: translate(-50%, -50%) translateZ(var(--z, 0px)) rotateZ(var(--rz, 0deg));
  width: var(--w, 46px); height: var(--h, 58px);
  background: var(--c, #fff);
  box-shadow: 0 1px 0 rgba(0,0,0,.14);
}
/* thin upright bar for the dial notch */
.notch {
  position: absolute; left: 50%; top: 50%;
  width: 5px; height: 17px; border-radius: 3px;
  transform: translate(-50%, -50%) translateZ(var(--z, 0px));
  background: var(--c, #e8a24d);
}
/* ruled lines on the report sheet */
.rule {
  position: absolute; left: 50%; border-radius: 2px; height: 3px;
  transform: translate(-50%, 0) translateZ(var(--z, 0px));
  top: var(--t, 12px); width: var(--w, 26px);
  background: var(--c, #b9bec6);
}

@media (prefers-reduced-motion: reduce) {
  .pick-wrap:hover .obj, .pick:focus-visible .obj {
    animation: none;
    transform: rotateX(62deg) rotateZ(24deg) scale(1.4);
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   SEARCH SCREEN
   Its own page: plain white, no chrome, no scenery. A thin bar at the top,
   the search field in the upper middle, the objects in the lower middle.
   Colours here are literal rather than tokens — this screen stays white when
   the rest of the app follows the sky into night.
   ═══════════════════════════════════════════════════════════════════════ */

.home2 {
  position: fixed; inset: 0; background: #fff; color: #1c1e21;
  display: flex; flex-direction: column; overflow: auto;
  font-family: var(--font-body);
}
.home2.has-ribbon { top: 30px; }

.home2-bar {
  flex: 0 0 auto; height: 70px; padding: 0 var(--space-6);
  display: flex; align-items: center; gap: var(--space-4);
  border-bottom: 1px solid #ececef;
}
.home2-brand {
  border: none; background: none; cursor: pointer; padding: 0;
  font-family: var(--font-display); font-size: 25px; color: #1c1e21;
  letter-spacing: -0.01em;
}
.home2-bar .spring { flex: 1; }
.home2-meta { font-size: 15px; color: #5c6169; }
/* The count is the way into the day, so it has to look like it can be pressed
   without becoming another button in a bar that already has three. */
.home2-meta-btn {
  border: 0; background: none; padding: 4px 8px; margin: -4px -8px;
  border-radius: 999px; font: inherit; font-size: 15px; cursor: pointer;
  transition: background .14s, color .14s;
}
.home2-meta-btn:hover { background: rgba(255,255,255,.10); color: #fff; }
.home2-btn {
  border: 1px solid #dfe1e5; background: #fff; cursor: pointer;
  font: inherit; font-size: 15px; font-weight: 600; color: #1c1e21;
  padding: 9px 19px; border-radius: 999px; transition: background .15s, border-color .15s;
}
.home2-btn:hover { background: #f6f7f8; border-color: #c9ccd1; }
.home2-btn.quiet { border-color: transparent; color: #6b7076; font-weight: 500; }
.home2-btn.quiet:hover { background: #f2f3f5; }
.home2-who { display: flex; align-items: center; gap: 10px; font-size: 15px; color: #33373d; }
.home2-who .avatar { width: 32px; height: 32px; background: #1c1e21; color: #fff; font-size: 13px; }

/* upper middle / lower middle */
.home2-body {
  flex: 1; display: flex; flex-direction: column; justify-content: flex-end;
  align-items: center; padding: clamp(28px, 6vh, 72px) var(--space-6) clamp(20px, 4vh, 44px);
  gap: var(--space-8);
}
/* Auto margins above and below let the field settle in the middle of the
   screen, while the objects stay pinned near the bottom. */
.bigsearch2 { margin-top: auto; margin-bottom: auto; }

.bigsearch2 { position: relative; width: min(660px, 92vw); }
.bigsearch2 { --bar-ink: #1c1e21; }
.bigsearch2 textarea {
  /* A textarea pretending to be a one-line field until it needs to be two.
     `resize:none` because the corner grip is a desktop affordance nobody
     wants on a search bar, and `overflow-y:auto` so a very long message
     scrolls rather than pushing the screen about. */
  display: block; resize: none; overflow-x: hidden; overflow-y: auto;
  max-height: 30vh;
  width: 100%; font: inherit; font-size: 19px; color: transparent;
  padding: 18px 72px 18px 26px; border-radius: 26px;
  background: #fff; border: 1px solid #dfe1e5;
  box-shadow: 0 1px 4px rgba(28,30,33,.06);
  caret-color: #1c1e21; transition: box-shadow .16s, border-color .16s;
}
.bigsearch2 textarea::placeholder { color: #9aa0a6; }
.bigsearch2 textarea:hover { box-shadow: 0 2px 10px rgba(28,30,33,.10); }
.bigsearch2 textarea:focus-visible {
  outline: none; border-color: #c3c7cc; box-shadow: 0 3px 14px rgba(28,30,33,.13);
}
/* The list sits below the field with a visual gap. That gap has to belong to
   the element, or the pointer leaves the wrapper on its way down and the list
   closes before it can be reached. A transparent top border provides the gap
   while keeping it inside the hover region; background-clip keeps the panel
   from painting into it. */
.acbox2 {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 30;
  border-top: 8px solid transparent;
  background: #fff; background-clip: padding-box;
  border-left: 1px solid #ececef; border-right: 1px solid #ececef; border-bottom: 1px solid #ececef;
  border-radius: 18px;
  box-shadow: 0 8px 28px rgba(28,30,33,.13); padding: 6px; overflow: hidden;
}
.acbox2 .ac-head { color: #9aa0a6; }
.acbox2 .ac-item { color: #1c1e21; border-radius: 12px; }
.acbox2 .ac-item:hover, .acbox2 .ac-item.on { background: #f2f3f5; }
.acbox2 .ac-item .cn { color: #85898f; }
.acbox2 .ac-empty { color: #7b8087; }

/* objects sit straight on the white — no tray */
.picker2 { width: min(840px, 96vw); }
.picker2 .picker-row {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-4);
  perspective: 900px; perspective-origin: 50% 30%;
}
.picker2 .pick { color: #1c1e21; }
.picker2 .pick-label { color: #4e535a; font-size: 16.5px; font-weight: 600; letter-spacing: -0.005em; }
.picker2 .pick-wrap:hover .pick-label, .picker2 .pick:focus-visible .pick-label { color: #1c1e21; }
.picker2 .pick-arc::after { border-color: #1c1e21; }

/* ═══════════════════════════════════════════════════════════════════════
   THE STUDIO
   The objects are not icons on a background — they stand on a floor. The
   floor is a plane laid back into the distance with perspective, lit from
   the upper left, and each object casts a soft elliptical shadow onto it.
   Hovering lifts the object off the floor, so its shadow grows and softens
   the way a real one does.
   ═══════════════════════════════════════════════════════════════════════ */

.home2 {
  /* soft overhead key light, as in a photographic cove */
  background:
    radial-gradient(120% 80% at 50% -15%, #ffffff 0%, #fdfdfe 45%, #f6f6f8 100%);
}

/* The floor. Anchored to the bottom of the viewport and rotated about its own
   bottom edge, so that edge never comes into view — there is no band, no seam
   and no straight line anywhere. The mask fades it out upward and to the
   sides, which is what makes it read as an infinity cove rather than a stage. */
.home2::after {
  content: ''; position: fixed; z-index: 0; pointer-events: none;
  left: -30%; right: -30%; bottom: 0; height: 58vh;
  transform: perspective(760px) rotateX(74deg);
  transform-origin: 50% 100%;
  background:
    radial-gradient(ellipse 30% 42% at 50% 96%, rgba(255,255,255,.90), rgba(255,255,255,0) 66%),
    linear-gradient(to top, rgba(28,30,33,.125) 0%, rgba(28,30,33,.055) 32%, rgba(28,30,33,0) 72%);
  -webkit-mask-image: radial-gradient(ellipse 58% 96% at 50% 100%, #000 34%, transparent 80%);
          mask-image: radial-gradient(ellipse 58% 96% at 50% 100%, #000 34%, transparent 80%);
}
.home2-bar, .home2-body { position: relative; z-index: 1; }

.studio { position: relative; width: min(840px, 96vw); }

.studio .picker-row { position: relative; z-index: 1; }

/* ── the object's cast shadow, on the floor rather than on the object ── */
.stage { position: relative; }
.ground {
  position: absolute; left: 50%; top: 70%;
  width: 112px; height: 30px; border-radius: 50%;
  transform: translate(-50%, -50%) translateX(10px);
  background: radial-gradient(closest-side, rgba(28,30,33,.42), rgba(28,30,33,.14) 56%, rgba(28,30,33,0) 78%);
  filter: blur(3.5px);
  transition: transform .38s cubic-bezier(.2,.8,.25,1), width .38s, height .38s, opacity .38s, filter .38s;
  z-index: 0;
}
.pick-wrap:hover .ground, .pick:focus-visible .ground {
  /* lifted off the floor: larger, softer, weaker */
  width: 148px; height: 38px; opacity: .58;
  filter: blur(9px);
  transform: translate(-50%, -50%) translateX(18px);
}
.obj { position: relative; z-index: 1; }

/* lifted on hover, not merely scaled */
@keyframes objspin {
  from { transform: rotateX(62deg) translateZ(16px) rotateZ(0deg)   scale(2.05); }
  to   { transform: rotateX(62deg) translateZ(16px) rotateZ(360deg) scale(2.05); }
}

/* ── directional lighting on the solids ─────────────────────────────── */
/* Key from the upper left: a bright rim there, occlusion opposite. */
.disc {
  box-shadow:
    inset 2.5px 2.5px 5px rgba(255,255,255,.42),
    inset -2.5px -3px 6px rgba(0,0,0,.22),
    0 1px 0 rgba(0,0,0,.16);
}
.slab {
  box-shadow:
    inset 1.5px 1.5px 3px rgba(255,255,255,.55),
    inset -1.5px -2px 4px rgba(0,0,0,.14),
    0 1px 0 rgba(0,0,0,.14);
}
.notch { box-shadow: inset 1px 1px 2px rgba(255,255,255,.5), 0 1px 2px rgba(0,0,0,.3); }

@media (prefers-reduced-motion: reduce) {
  .pick-wrap:hover .obj, .pick:focus-visible .obj {
    transform: rotateX(62deg) translateZ(16px) rotateZ(24deg) scale(2.0);
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   CURSOR
   A grey disc that trails the pointer and stretches into a caret over any
   text field. It REPLACES the system cursor rather than accompanying it, so
   there is only ever one pointer on screen. On by default; Settings turns it
   off and gives the system cursor back.
   ═══════════════════════════════════════════════════════════════════════ */

body.cursor-custom, body.cursor-custom * { cursor: none !important; }

#cursor {
  position: fixed; left: 0; top: 0; z-index: 9999; pointer-events: none;
  width: 14px; height: 14px; border-radius: 50%;
  color: #6b6f76;                       /* drives the crosshair arms */
  background: currentColor; opacity: 0;
  /* A cursor that only works on one ground is not a cursor. The halo is the
     opposite tone to the fill, so the shape keeps an edge whether it is over a
     photograph, a grey panel, or a white input. */
  filter: drop-shadow(0 0 1px rgba(255,255,255,.95))
          drop-shadow(0 0 2.5px rgba(255,255,255,.6))
          drop-shadow(0 1px 2px rgba(16,14,12,.35));
  /* The click pulse is NOT here. `scale` as its own property is applied before
     `transform`, so it multiplied the follow loop's translate and flung the
     cursor across the screen. It rides inside the transform string instead —
     see customCursor() in app.js. */
  transition: width .2s cubic-bezier(.2,.9,.3,1.2),
              height .2s cubic-bezier(.2,.9,.3,1.2),
              border-radius .2s, background .2s, opacity .2s;
}
body.cursor-custom #cursor.on { opacity: 1; }

/* The two arms live on the pseudo-elements so the disc can shrink out of the
   way underneath them — one element morphing rather than two swapping. */
#cursor::before, #cursor::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  background: currentColor; border-radius: 999px;
  width: 0; height: 0; opacity: 0;
  transition: width .2s cubic-bezier(.2,.9,.3,1.2),
              height .2s cubic-bezier(.2,.9,.3,1.2), opacity .16s;
}
/* clickable: the disc collapses and a rounded crosshair opens in its place */
#cursor.tap { width: 5px; height: 5px; background: transparent; }
#cursor.tap::before { width: 24px; height: 3px; opacity: 1; }
#cursor.tap::after  { width: 3px; height: 24px; opacity: 1; }
/* declared after .tap so the caret always wins over a text field */
#cursor.text { width: 2px; height: 18px; border-radius: 1.5px; background: currentColor; }
#cursor.text::before, #cursor.text::after { width: 0; height: 0; opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  #cursor { transition: opacity .2s; }
  #cursor::before, #cursor::after { transition: opacity .2s; }
}

/* ═══════════════════════════════════════════════════════════════════════
   CLIENT BAR
   The Fieldbook client picker, kept in the same visual language but laid
   across the top of the client's page rather than down its side. Below it
   the page shows only that client's own record.
   ═══════════════════════════════════════════════════════════════════════ */

.clientbar {
  background: var(--color-surface); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm); padding: var(--space-3);
  display: flex; align-items: center; gap: var(--space-3);
  margin-bottom: var(--space-6);
}
.clientbar-find { width: 208px; flex-shrink: 0; }
.clientbar-find .input { font-size: 13.5px; min-height: 34px; }
.clientbar-count {
  font-size: 11.5px; color: var(--color-neutral-600);
  white-space: nowrap; flex-shrink: 0;
}
.clientbar-rows {
  display: flex; gap: 6px; flex: 1; min-width: 0;
  overflow-x: auto; scroll-behavior: smooth;
  padding: 2px 2px 6px;
}
.clientbar-rows::-webkit-scrollbar { height: 6px; }
.clientbar-rows::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--color-text) 18%, transparent); border-radius: 999px;
}

.client-pill {
  display: inline-flex; align-items: center; gap: 8px; flex-shrink: 0;
  padding: 7px 14px; border-radius: 999px; cursor: pointer;
  background: transparent; border: 1px solid transparent;
  font: inherit; color: var(--color-text); white-space: nowrap;
  transition: background .14s, border-color .14s;
}
.client-pill:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.client-pill.on {
  background: var(--color-accent-100);
  border-color: var(--color-accent-300);
}
.client-pill .cp-name { font-weight: 600; font-size: 13.5px; }
.client-pill .cp-case {
  font-size: 12px; color: var(--color-neutral-600); font-variant-numeric: tabular-nums;
}
.client-pill .lr-dot {
  width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
  background: var(--color-neutral-400);
}
.client-pill.logged .lr-dot { background: var(--color-accent-2-600); }

@media (max-width: 860px) {
  .clientbar { flex-direction: column; align-items: stretch; }
  .clientbar-find { width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════════
   CLEAN SKIN
   The same white, charcoal-on-white aesthetic as the search screen, applied
   by redefining the tokens on the shell itself. Everything inside is built
   from those tokens, so the whole page follows without touching a single
   component rule. Defined on a class, so it also overrides the sky theme
   engine's values on :root for this subtree.
   ═══════════════════════════════════════════════════════════════════════ */

.shell.clean {
  position: relative; z-index: 1;
  --color-bg:          #ffffff;
  --color-surface:     #fafafb;
  --color-text:        #1c1e21;
  --color-divider:     #e6e7ea;
  --color-neutral-100: #ffffff;
  --color-neutral-200: #f1f2f4;
  --color-neutral-300: #e3e5e8;
  --color-neutral-400: #c9ccd1;
  --color-neutral-500: #a6abb2;
  --color-neutral-600: #7b8087;
  --color-neutral-700: #5c6169;
  --color-neutral-800: #3a3e44;
  --color-neutral-900: #1c1e21;
  --brand:             #1c1e21;
  --color-accent:      #1c1e21;   /* buttons and focus go charcoal, not terracotta */
  --color-accent-600:  #34383e;
  --color-accent-700:  #1c1e21;
  --font-heading: "Figtree", system-ui, -apple-system, sans-serif;
  --font-heading-weight: 700;
  background: #ffffff;
}
.shell.clean .content { padding-top: var(--space-8); }
.shell.clean h2, .shell.clean h3 { letter-spacing: -0.015em; }
/* keep the status chips coloured — they are the one place colour carries meaning */
.shell.clean .tag { font-weight: 600; }

/* ── case facts under the client's name ─────────────────────────────── */
.casefacts {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-top: var(--space-3);
}
.fact {
  display: inline-flex; align-items: baseline; gap: 7px;
  background: var(--color-neutral-200); border-radius: 999px;
  padding: 5px 13px; font-size: 13.5px;
}
.fact-k {
  font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--color-neutral-600); font-weight: 600;
}
.fact-v { color: var(--color-text); font-weight: 600; }

/* ═══════════════════════════════════════════════════════════════════════
   CLIENT OVERVIEW
   The caseload as objects on a surface: one figure per ten clients, grouped
   by where those cases are. Search sits top right with the history menu.
   ═══════════════════════════════════════════════════════════════════════ */

.ov-head {
  display: flex; align-items: flex-start; gap: var(--space-4);
  margin-bottom: var(--space-6); flex-wrap: wrap;
}
.ov-head h2 { font-size: 26px; margin: 0 0 3px; }
.ov-head .sub { font-size: 13.5px; color: var(--color-neutral-600); }
.ov-tools { margin-left: auto; display: flex; align-items: center; gap: var(--space-2); }
.ov-search { position: relative; width: 264px; }
.ov-search input {
  width: 100%; font: inherit; font-size: 14.5px; padding: 10px 18px;
  border-radius: 999px; background: #fff; border: 1px solid #dfe1e5;
  box-shadow: 0 1px 3px rgba(28,30,33,.05); color: var(--color-text);
}
.ov-search input:focus-visible { outline: none; border-color: #c3c7cc; box-shadow: 0 2px 10px rgba(28,30,33,.10); }
.ov-search .acbox2 { top: 100%; }

@media (max-width: 640px) {
  /* .ov-head wraps, so the toolbar drops below the heading — but .ov-tools
     itself does not, and the search box holds a fixed 264px. With the Recent
     chip beside it that comes to 373px inside a 340px card. The same failure
     as the rest of this pass: nothing scrolled sideways, the page widened
     and the browser zoomed the whole screen out to fit. */
  .ov-tools { margin-left: 0; width: 100%; flex-wrap: wrap; }
  .ov-search { width: 100%; }
}

/* ── history menu, opens on hover ─────────────────────────────────────── */
.histwrap { position: relative; }
.histbtn {
  display: inline-flex; align-items: center; gap: 7px; cursor: default;
  border: 1px solid #dfe1e5; background: #fff; border-radius: 999px;
  padding: 10px 15px; font: inherit; font-size: 14px; color: var(--color-text);
}
.histbtn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.9;
               stroke-linecap: round; stroke-linejoin: round; }
.histwrap:hover .histbtn { background: #f6f7f8; }
.histmenu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
  min-width: 250px; background: #fff; border: 1px solid #ececef;
  border-radius: 16px; box-shadow: 0 10px 30px rgba(28,30,33,.15);
  padding: 6px; opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: opacity .14s, transform .18s cubic-bezier(.2,.9,.3,1.2), visibility .14s;
}
/* a small invisible bridge so the pointer can cross the gap */
.histwrap::after { content: ''; position: absolute; right: 0; top: 100%; width: 100%; height: 10px; }
.histwrap:hover .histmenu, .histwrap:focus-within .histmenu {
  opacity: 1; visibility: visible; transform: none;
}
.histmenu .ac-head { padding: 7px var(--space-3) 5px; }
.hist-item {
  display: flex; align-items: baseline; gap: 9px; width: 100%;
  border: none; background: transparent; cursor: pointer; text-align: left;
  font: inherit; font-size: 14px; color: var(--color-text);
  padding: 9px var(--space-3); border-radius: 11px;
}
.hist-item:hover { background: #f2f3f5; }
.hist-item .when { margin-left: auto; font-size: 12px; color: var(--color-neutral-600); }
.hist-empty { padding: var(--space-3); font-size: 13.5px; color: var(--color-neutral-600); text-align: center; }

/* ── the groups ───────────────────────────────────────────────────────── */
.ov-stage {
  display: flex; align-items: flex-end; justify-content: center;
  gap: clamp(24px, 6vw, 90px); flex-wrap: wrap;
  padding: clamp(30px, 7vh, 80px) 0 clamp(40px, 8vh, 90px);
  perspective: 1100px; perspective-origin: 50% 34%;
  position: relative;
  /* The floor below reaches 20% past each edge, and an absolutely positioned
     box still contributes to its container's scrollable overflow — so on a
     phone the decoration widened the page and the browser zoomed the whole
     screen out to 425px to fit it. `clip` rather than `hidden` because it is
     the one value that pairs with overflow-y: visible, and the cards are
     meant to overhang this box vertically. */
  overflow-x: clip;
}
/* the floor, same infinity-cove treatment as the search screen */
.ov-stage::after {
  content: ''; position: absolute; z-index: 0; pointer-events: none;
  left: -20%; right: -20%; bottom: 0; height: 320px;
  transform: perspective(620px) rotateX(75deg);
  transform-origin: 50% 100%;
  background:
    radial-gradient(ellipse 34% 46% at 50% 92%, rgba(255,255,255,.9), rgba(255,255,255,0) 68%),
    linear-gradient(to top, rgba(28,30,33,.10) 0%, rgba(28,30,33,.04) 34%, rgba(28,30,33,0) 74%);
  -webkit-mask-image: radial-gradient(ellipse 54% 82% at 50% 100%, #000 30%, transparent 78%);
          mask-image: radial-gradient(ellipse 54% 82% at 50% 100%, #000 30%, transparent 78%);
}

.ovgroup {
  position: relative; z-index: 1; display: flex; flex-direction: column;
  align-items: center; gap: var(--space-3);
  background: none; border: none; cursor: pointer; font: inherit; padding: 0;
}
.ovgroup:focus-visible { outline: 2px solid var(--color-text); outline-offset: 6px; border-radius: 18px; }
.ov-cluster {
  position: relative; width: 268px; height: 214px;
  transform-style: preserve-3d;
  transition: transform .32s cubic-bezier(.2,.8,.25,1);
}
.ovgroup:hover .ov-cluster, .ovgroup:focus-visible .ov-cluster { transform: scale(1.09); }

/* each figure floats on its own rhythm */
.ovfig {
  position: absolute; width: 66px; height: 66px;
  transform-style: preserve-3d;
  animation: bob 4.6s ease-in-out infinite;
}
.ovgroup:hover .ovfig { animation: bob 4.6s ease-in-out infinite, groupspin 7s linear infinite; }
@keyframes bob {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -9px; }
}
@keyframes groupspin {
  from { rotate: y 0deg; }
  to   { rotate: y 360deg; }
}
.ovfig .inner {
  position: absolute; inset: 0; transform-style: preserve-3d;
  transform: rotateX(62deg);
}
.ovfig .fig-shadow {
  position: absolute; left: 50%; top: 78%;
  width: 52px; height: 15px; border-radius: 50%;
  transform: translate(-50%, -50%) translateX(5px);
  background: radial-gradient(closest-side, rgba(28,30,33,.30), rgba(28,30,33,0) 76%);
  filter: blur(2.5px);
}

.ov-label { text-align: center; }
.ov-count {
  display: block; font-family: var(--font-heading); font-weight: 700;
  font-size: 34px; line-height: 1; color: var(--color-text);
}
.ov-name { display: block; font-size: 15.5px; font-weight: 600; color: var(--color-neutral-800); margin-top: 5px; }
.ov-sub { display: block; font-size: 12.5px; color: var(--color-neutral-600); margin-top: 3px; }
.ov-legend {
  text-align: center; font-size: 13px; color: var(--color-neutral-600);
  margin-top: var(--space-4);
}

@media (prefers-reduced-motion: reduce) {
  .ovfig, .ovgroup:hover .ovfig { animation: none; }
  .ov-cluster { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   BACKDROP
   Off by default. When on, a photograph sits behind the spacious screens and
   the surfaces above it become frosted glass — light translucent grey with a
   white highlight along the top edge, so type stays dark-on-light and legible.

   Deliberately limited to the search and overview screens. Behind a dense
   table it would cost contrast on exactly the screens that matter most.
   ═══════════════════════════════════════════════════════════════════════ */

/* The photograph gets its own fixed layer so it can be softened and
   desaturated — a sharp, saturated image competes with the interface for
   attention, and this is meant to be a backdrop, not a subject. */
body.has-backdrop.bd-screen::before {
  content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-image: var(--backdrop-img, none);
  background-size: cover; background-position: center;
  background-color: #eeece7;    /* if the file is missing, a plain wash */
  filter: saturate(.62) brightness(1.1) blur(2px);
  transform: scale(1.02);       /* hides the blur's soft edge */
}
/* the wash that keeps type dark-on-light */
body.has-backdrop .home2 { background: rgba(250,250,251,.62); position: fixed; z-index: 1; }

body.has-backdrop .home2-bar {
  background: rgba(255,255,255,.52);
  -webkit-backdrop-filter: blur(22px) saturate(1.25);
          backdrop-filter: blur(22px) saturate(1.25);
  border-bottom-color: rgba(255,255,255,.55);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.75);
}
body.has-backdrop .bigsearch2 textarea,
body.has-backdrop .ov-search input {
  background: rgba(255,255,255,.58);
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
          backdrop-filter: blur(18px) saturate(1.2);
  border-color: rgba(255,255,255,.7);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.8), 0 2px 12px rgba(28,30,33,.10);
}
body.has-backdrop .acbox2,
body.has-backdrop .histmenu {
  background: rgba(255,255,255,.72);
  -webkit-backdrop-filter: blur(26px) saturate(1.25);
          backdrop-filter: blur(26px) saturate(1.25);
  border-color: rgba(255,255,255,.7);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.85), 0 10px 34px rgba(28,30,33,.16);
}
body.has-backdrop .fan-label,
body.has-backdrop .fan-btn,
body.has-backdrop .home2-btn,
body.has-backdrop .histbtn {
  background: rgba(255,255,255,.62);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
          backdrop-filter: blur(16px) saturate(1.2);
  border-color: rgba(255,255,255,.7);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.8), 0 3px 12px rgba(28,30,33,.12);
}
/* the objects gain a little contrast against a busier ground */
body.has-backdrop .obj { filter: drop-shadow(0 6px 16px rgba(28,30,33,.28)); }
body.has-backdrop .picker2 .pick-label,
body.has-backdrop .ov-name, body.has-backdrop .ov-count {
  text-shadow: 0 1px 10px rgba(255,255,255,.9), 0 1px 2px rgba(255,255,255,.9);
}
body.has-backdrop .ground { opacity: .8; }

/* the overview screen, same treatment inside the shell */
body.has-backdrop .shell.on-backdrop { background: rgba(250,250,251,.66); position: relative; z-index: 1; }
body.has-backdrop .shell.on-backdrop .sidebar {
  background: rgba(255,255,255,.55);
  -webkit-backdrop-filter: blur(22px) saturate(1.25);
          backdrop-filter: blur(22px) saturate(1.25);
  border-right-color: rgba(255,255,255,.5);
}
body.has-backdrop .shell.on-backdrop .ov-stage::after { opacity: .55; }

/* ── settings screen ─────────────────────────────────────────────────── */
.setblock {
  background: var(--color-surface); border: 1px solid #eeeff1;
  border-radius: var(--radius-lg); padding: var(--space-4) var(--space-6);
  margin-bottom: var(--space-4); max-width: 760px;
}
.setblock h3 { font-size: 17px; margin: 0 0 3px; }
.setblock .why { font-size: 13.5px; color: var(--color-neutral-600); margin: 0 0 var(--space-4); max-width: 62ch; }
.setrow { display: flex; align-items: center; gap: var(--space-4); }
.setrow .grow { flex: 1; }

.switch {
  position: relative; width: 50px; height: 29px; flex-shrink: 0;
  border-radius: 999px; border: 1px solid var(--color-neutral-300);
  background: var(--color-neutral-200); cursor: pointer; padding: 0;
  transition: background .18s, border-color .18s;
}
.switch::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 23px; height: 23px; border-radius: 50%; background: #fff;
  box-shadow: 0 1px 3px rgba(28,30,33,.3);
  transition: transform .2s cubic-bezier(.2,.9,.3,1.3);
}
.switch.on { background: #1c1e21; border-color: #1c1e21; }
.switch.on::after { transform: translateX(21px); }

.presets { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-top: var(--space-4); }
.preset {
  width: 128px; border: 2px solid transparent; border-radius: 14px;
  overflow: hidden; cursor: pointer; background: none; padding: 0; font: inherit;
}
.preset .swatch {
  display: block; height: 78px; background-size: cover; background-position: center;
  background-color: #e6e4de; border-radius: 12px;
}
.preset .swatch { position: relative; }
/* A film and a still are the same thing in a 78px square. This says which. */
.preset .pmoves {
  position: absolute; left: 6px; bottom: 6px;
  padding: 1px 6px; border-radius: 999px;
  font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
  background: rgba(20,19,17,.62); color: #f4f3f0;
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.preset .pname { display: block; font-size: 12.5px; padding: 6px 2px 0; color: var(--color-neutral-700); }
.preset.on { border-color: #1c1e21; }
.preset.on .pname { color: var(--color-text); font-weight: 600; }

/* ── live preview in settings ────────────────────────────────────────── */
.bd-preview {
  position: relative; height: 210px; border-radius: 16px; overflow: hidden;
  border: 1px solid var(--color-neutral-300); margin-top: var(--space-4);
  background: #eeece7;
}
.bd-preview .shot {
  position: absolute; inset: 0;
  background-size: cover; background-position: center;
  filter: saturate(.62) brightness(1.1) blur(2px); transform: scale(1.04);
}
.bd-preview .wash { position: absolute; inset: 0; background: rgba(250,250,251,.62); }
.bd-preview.plain .shot { display: none; }
.bd-preview.plain .wash { background: #fff; }
.bd-preview .chrome {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: space-between; padding: 18px 20px 22px;
}
.bd-preview .mini-bar {
  align-self: stretch; height: 26px; border-radius: 8px;
  background: rgba(255,255,255,.55);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  border: 1px solid rgba(255,255,255,.6);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.8);
  display: flex; align-items: center; padding: 0 10px;
  font-size: 10px; font-weight: 700; color: #1c1e21; letter-spacing: -.01em;
}
.bd-preview .mini-search {
  width: 62%; height: 34px; border-radius: 999px;
  background: rgba(255,255,255,.6);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  border: 1px solid rgba(255,255,255,.7);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.85), 0 2px 10px rgba(28,30,33,.1);
  display: flex; align-items: center; padding: 0 14px;
  font-size: 11px; color: #6b7076;
}
.bd-preview .mini-objs { display: flex; gap: 26px; }
.bd-preview .mini-obj {
  width: 22px; height: 22px; border-radius: 50% 50% 44% 44%;
  box-shadow: 0 4px 8px rgba(28,30,33,.28), inset 1px 1px 2px rgba(255,255,255,.5);
}
.bd-preview .caption {
  position: absolute; right: 10px; bottom: 8px;
  font-size: 10.5px; color: #5c6169;
  background: rgba(255,255,255,.7); border-radius: 999px; padding: 3px 9px;
}

/* ═══════════════════════════════════════════════════════════════════════
   GLASS SKIN
   The photograph fills the window behind everything except the sidebar; the
   content sits on translucent warm-grey panels with light type, the way the
   reference mockup does.

   Done by redefining the design tokens rather than rewriting components —
   every panel, table, input and chip is already built from these, so the
   whole app follows. Layout is untouched: this changes colour and material
   only.
   ═══════════════════════════════════════════════════════════════════════ */

body.glass {
  --glass:        rgba(66, 63, 59, .70);   /* panels over the photo */
  /* Opaque, not nearly-opaque. Even three per cent of a high-contrast
     photograph reads as mottling under small type on a dark panel — the eye
     is very sensitive to it at low luminance. Glass belongs on chrome; a
     table of case notes gets a solid ground. */
  --glass-strong: #34322e;
  --glass-solid:  #423f3b;                 /* the sidebar, which is opaque */
  --hair:         rgba(255, 255, 255, .16);
  --lift:         inset 0 1px 0 rgba(255,255,255,.20);
}

body.glass .shell.clean,
body.glass .shell {
  --color-bg:          transparent;
  --color-surface:     var(--glass);
  --color-text:        #f4f3f0;
  --color-divider:     var(--hair);
  --color-neutral-100: rgba(255,255,255,.06);
  --color-neutral-200: rgba(255,255,255,.10);
  --color-neutral-300: rgba(255,255,255,.18);
  --color-neutral-400: rgba(255,255,255,.32);
  --color-neutral-500: rgba(244,243,240,.50);
  --color-neutral-600: rgba(244,243,240,.64);
  --color-neutral-700: rgba(244,243,240,.78);
  --color-neutral-800: rgba(244,243,240,.90);
  --color-neutral-900: #f4f3f0;
  --brand:             #f4f3f0;
  --color-accent:      rgba(255,255,255,.20);
  --color-accent-600:  rgba(255,255,255,.28);
  --color-accent-700:  #f4f3f0;
  background: transparent;
  /* `body { color: var(--color-text) }` resolves against the ROOT token, so
     anything inheriting plain `color` would still get the light theme's
     charcoal. Set it here, where the glass tokens actually live. */
  color: var(--color-text);
}

/* the photograph, behind everything */
body.glass::before {
  content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  /* A wash over the photograph. Without it the panels' blur picks up whatever
     is behind them, so a control over bright sky reads almost white while the
     same control over a leaf reads black. The wash flattens that range so the
     glass is a consistent material. */
  background-image:
    linear-gradient(rgba(38, 36, 33, .44), rgba(38, 36, 33, .44)),
    var(--backdrop-img, none);
  background-size: cover; background-position: center;
  background-color: #6d675e;
  /* The saturate(.72) that used to live here is baked into the files — see
     scripts/bake-saturation.py. It was free on this one element and ruinous on
     the sign-in grid, which puts the same photograph on 228 windows. */
}
body.glass.no-photo::before { background-image: none; background-color: #55514b; }

/* ── the moving backdrop ───────────────────────────────────────────────────
   A film where the photograph would be. Same place in the stacking order,
   same wash over it, and nothing above it changes.

   The wash is the reason this is a wrapper and not a bare <video>. Every
   panel in this scheme is translucent dark glass with light type on it, and
   that only works because `body.glass::before` lays a flat 44% dark over the
   picture first. A video cannot be put behind that pseudo-element — both are
   fixed children of the body at z-index 0, so paint order is tree order and
   ::before is always first, which means the film would cover the very thing
   that makes the interface readable. So the wash comes with the film, as its
   own ::after, and `has-film` empties the one underneath.

   `object-fit: cover` for the same reason the photographs use
   `background-size: cover`: the frame is 2.21:1 and a window is whatever
   shape somebody dragged it into. Letterboxing a backdrop would put two
   black bars inside the room. */
/* ...but not while the film is still fading up out of the sign-in room. The
   room's photograph has to stay under it for the length of the cross-fade,
   or the film arrives over a bare white body instead of out of the wall it
   is replacing. */
body.has-film.glass:not(.film-arriving)::before {
  background-image: none; background-color: transparent;
}

.filmroom {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background: #55514b;              /* until the first frame decodes */
  overflow: hidden;
}
.filmroom video {
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* The sun answering the music — see FLASH in app.js, where the numbers are
     measured off the clip. Set on the VIDEO rather than on .filmroom so the
     wash above it is untouched: brightening that too would lift the whole
     screen and take the panels' contrast with it.

     The transition is what makes it smooth. The value is written every fifty
     milliseconds from the audio's own clock, and eighty milliseconds of
     linear easing joins those samples into a curve instead of a staircase. */
  filter: brightness(var(--film-flash, 1));
  transition: filter 80ms linear;
  /* It is scenery. It cannot be clicked, focused, or reached by tabbing —
     the tabIndex is set in JS as well, because `pointer-events` says nothing
     about the keyboard. */
  pointer-events: none;
}
/* The wash, over the film rather than under it. */
.filmroom::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: rgba(38, 36, 33, .44);
}
/* Off the sign-in screen, which is its own black room with its own light
   coming up. A film playing behind that is a second thing happening during
   the one moment the screen is meant to be still. */
body.at-login .filmroom { display: none; }

/* And off the screens the film does not run behind — see FILMLESS in
   app.js. Hidden here, paused there, and both are needed: display:none
   stops the compositing but does not reliably stop a <video> decoding, and
   pausing alone leaves the last frame painted. */
.filmroom.film-hidden { display: none; }

/* Arriving. The sign-in screen is the daylight wall with the light at the
   top of every dial; signing in turns it into the film, and it should turn
   rather than cut. 1.2s, matched by FILM_FADE in app.js — change both. */
.filmroom { transition: opacity 1.2s ease; }
.filmroom.arriving { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  /* The film itself is already a still under this setting. A backdrop that
     dissolves into another backdrop is exactly the kind of movement the
     setting is asking not to have — and neither is a room that brightens at
     you, which is the one thing on this screen somebody might have turned
     the setting on because of. */
  .filmroom { transition: none; }
  .filmroom.arriving { opacity: 1; }
  .filmroom video { filter: none; transition: none; }
}

/* The ground it leaves behind.

   Not a nicety. Under a film `--backdrop-img` is deliberately `none`
   (app.js — a still copy of a moving frame is wrong from the next frame
   onward) and `body.has-film.glass::before` is transparent, so with
   .filmroom hidden there is nothing left between the panels and `body`,
   whose background is --color-bg: white. Without this rule, leaving the
   film flashes the whole screen white behind a near-black panel.

   #0a0c0d is the palette's own panel colour, so the screen reads as a
   deliberate dark neutral rather than as a backdrop that failed to load. */
body.film-off { background: var(--bare-ground, #0a0c0d); }

/* And the photograph too, on those screens. `body.film-off` alone loses to
   `body.glass.no-photo::before` and to the has-film rule above, both of
   which carry two classes — so this carries two as well and sits after
   them. Without it a still backdrop stayed put on Reminders and only the
   film went, which is half a rule. */
/* Two selectors, and the second is the one that matters.

   `body.has-film.glass:not(.film-arriving)::before` above carries three
   classes and an element; a bare `body.glass.film-off::before` carries two,
   loses, and leaves this layer TRANSPARENT. The ground then exists only as
   the body's background, which paints the canvas — and the canvas is not
   part of the backdrop root that `backdrop-filter` samples.

   The symptom was the sidebar. It is a translucent bar with a blur behind
   it, so with nothing in the backdrop to sample it filtered transparent
   black and came out dark, on a screen whose ground is Grey Nickel. Every
   measurement said the bar was light — computed background, the elements
   under it, every layer transparent — because every one of them was. What
   was dark was the absence.

   Matching the count and sitting later wins, and puts the ground somewhere
   a blur can see it. */
body.glass.film-off::before,
body.has-film.glass.film-off::before {
  background-image: none; background-color: var(--bare-ground, #0a0c0d);
}

/* ── afternoon ─────────────────────────────────────────────────────────────
   Two things, both switchable, both off in one class.

   THE DRIFT. The LIGHT moves. The photograph does not, and that correction is
   the whole of this section's history.

   It used to move the photograph itself, on the reasoning written here at the
   time: these backdrops are shadow cast on a plain surface, and a flat surface
   has no detail to reveal that the whole frame is moving. That was true of the
   backdrops it was written for — leaf shadow on a blank wall — and it stopped
   being true the moment a room with a floor in it became the default. A wall
   meeting a floor is a hard horizontal line across the picture, and a hard
   horizontal line reports every pixel of vertical drift. Between the three
   animations the frame wandered about thirty pixels up and down: the breathe
   alone scaled it 3.3%, which lifts and drops that line by a dozen pixels on
   its own. What read as weather on a blank wall reads as the camera bobbing
   in a room, and the floor appears to rise and fall.

   So the photograph is now nailed down and only the warm layer drifts across
   it. That layer is a copy of the same picture used to place the warmth, so
   moving it slides the warmth off the bright patches it was cut from — which
   is not a defect here, it is the effect. A warm edge creeping over the lip of
   a shadow is what a branch moving in front of a window actually does. The
   room stays exactly where it is.

   Three periods that do not divide into each other, so the loop never lands in
   the same place twice within a working day.

   It is a transform on ONE fixed element, which the compositor handles on the
   GPU without repainting anything. That matters here: a filter on this layer
   was ruinous once already, and this runs continuously rather than for the
   length of a transition.

   The base scale is what makes it safe. At 1.06 the layer is 6% larger than
   the viewport, so a drift of ±1.4% can never pull an edge into view. */
/* The filter lives in the document but must never take up space or catch a
   pointer. Zero-sized and fixed, so it cannot lengthen the page. */
.wxfilter { position: fixed; width: 0; height: 0; overflow: hidden;
            pointer-events: none; }

/* ── the wind in the leaves ────────────────────────────────────────────────
   The shadows on the wall are pixels of the photograph, so nothing that
   leaves the photograph alone can make them move. Displacing the warm layer
   instead was tried first and is not worth having: at a third of an opacity,
   screened, over shadows this soft, warping it by thirty pixels is invisible.

   So this is a second copy of the photograph, warped, laid exactly over the
   first — and MASKED so it exists only on the wall. It fades out well above
   the line where the wall meets the floor, and below that the untouched
   original shows through. The shadows flutter; the floor cannot move,
   because down there this layer is not present at all.

   At displacement zero the copy is pixel-identical to what is underneath it,
   so "Still" costs nothing and shows nothing. */
.leafwind {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(rgba(38, 36, 33, .44), rgba(38, 36, 33, .44)),
    var(--backdrop-img, none);
  background-size: cover; background-position: center;
  scale: 1.06;
  /* Solid across the wall, gone before the floor. The fade is long so there
     is no seam to find. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 48%, transparent 70%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 48%, transparent 70%);
  display: none;
}
/* ── the light belongs to one room ─────────────────────────────────────────
   `lit-room` is on the body only while the Daylight backdrop is showing, and
   every layer below it carries the class.

   It is not a tidying-up. All of this is made OUT of the photograph — the
   wind is a second warped copy of it, the sun is an orange fill multiplied by
   it and screened back over it — and that only reads as light on the white
   studio wall it was built from. Over a ballpark or a canal it is a detailed
   picture sliding a few pixels over itself and being tinted in patches, which
   does not look like weather; it looks like the photograph is broken.
   ────────────────────────────────────────────────────────────────────────── */
body.glass.lit-room.light-moves .leafwind { display: block; filter: url(#leafwind); }
/* All three of these have to out-specify the rule above, which is
   `body.glass.light-moves .leafwind` at three classes. `body.at-login
   .leafwind` is only two, so it lost — and the login screen, which is meant
   to be black, was wearing the top seventy per cent of the photograph with
   the mask's fade as the join. That is the half-black screen. */
/* Not excluded from the sign-in screen any more. That screen runs the wind
   at maximum now, and the wind IS this layer — hiding it there would be
   turning off the thing that was asked for. */
body.glass.lit-room.light-moves .leafwind.calm,
body.glass.lit-room.light-moves.no-photo .leafwind { display: none; }

/* Deliberately not animated. See above: this is the room, and the room holds
   still. It keeps the oversized scale so that switching the movement on and
   off does not change the framing. */
body.glass.light-moves::before { scale: 1.06; }
@keyframes leafDrift {
  0%   { translate: calc(-1.1% * var(--leaf-amp)) calc(0.5% * var(--leaf-amp)); }
  38%  { translate: calc(0.7% * var(--leaf-amp)) calc(-0.9% * var(--leaf-amp)); }
  71%  { translate: calc(-0.4% * var(--leaf-amp)) calc(1.2% * var(--leaf-amp)); }
  100% { translate: calc(1.3% * var(--leaf-amp)) calc(-0.3% * var(--leaf-amp)); }
}
/* Safe to vary again. This only ever scales the WARM layer now, and growing
   the light is light spreading, not the camera pushing in — the room it falls
   on is a separate element and is not animated at all. */
@keyframes leafBreathe {
  0%   { scale: 1.06; }
  46%  { scale: calc(1.06 + .035 * var(--leaf-amp)); }
  100% { scale: calc(1.06 + .005 * var(--leaf-amp)); }
}
@keyframes leafTilt {
  0%   { rotate: calc(-0.55deg * var(--leaf-amp)); }
  55%  { rotate: calc(0.35deg * var(--leaf-amp)); }
  100% { rotate: calc(-0.15deg * var(--leaf-amp)); }
}
/* Not animated, but still oversized, so switching the movement off does not
   snap the framing. */
body.glass::before { scale: 1.06; }

/* THE SUN. Cut from the photograph itself rather than placed by hand, so the
   warmth lands exactly where the light does.

   Hand-positioned gradients could only ever approximate it, and the
   approximation showed: amber sitting on a grey corner while the real bright
   band ran somewhere else. Nine presets put their light in nine different
   places, so no set of coordinates could be right for all of them.

   Two blends do it. INSIDE the layer, an orange fill is multiplied by the
   photograph — multiply keeps orange where the photo is bright and falls to
   black where it is dark, which makes the picture its own mask. That result is
   then SCREENED over the scene, and screen ignores black, so the shadows are
   untouched and only the lit patches take the colour.

   It carries the same drift as the photograph rather than one of its own. The
   two have to move as one thing: the warmth belongs to the light spots, and a
   tint drifting at its own pace slides off them. */
body.glass.lit-room::after {
  content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(rgb(var(--wx-warm)), rgb(var(--wx-warm))),
    var(--backdrop-img, none);
  background-blend-mode: multiply;
  background-size: cover; background-position: center;
  mix-blend-mode: screen;
  /* Enough to read as late light rather than as a colour cast over the whole
     picture. The screen blend is doing the placing; this only sets how much. */
  opacity: var(--wx-sun);
  scale: 1.06;
}
/* The `body.glass.no-photo::after` guard that used to sit here is gone with
   the reason for it: `lit-room` is only ever set with the backdrop ON, so
   there is no longer a state where this layer exists with no photograph to
   multiply against. A rule that cannot match is worse than no rule — it
   reads as a case being handled. */
/* ── the weather, as three numbers ─────────────────────────────────────────
   Temperature, wind and cloud are not three effects. They are three inputs to
   the two layers that were already here, so all three are custom properties
   and nothing swaps at runtime.

   --wx-warm / --wx-sun    how orange the sunlit patches are, and how much
   --wx-cool               how heavy the overcast wash gets
   --wx-period             one weather cycle; more cloud means a shorter one
   --wx-drift              how fast the leaf shadows move
   --leaf-amp              and how far

   The amplitudes are read inside the keyframes with calc(). A custom property
   in a keyframe is substituted rather than interpolated, which is exactly
   right here — the wind does not change during one pass of the shadows, it
   sets how big that pass is. */

body.glass.lit-room.light-moves::after {
  will-change: transform;
  animation: leafDrift var(--wx-drift) ease-in-out infinite alternate,
             leafBreathe calc(var(--wx-drift) * 1.65) ease-in-out infinite alternate,
             leafTilt calc(var(--wx-drift) * 1.16) ease-in-out infinite alternate,
             sunFade var(--wx-period) ease-in-out infinite;
}

/* THE TWO LOOPS THEMSELVES.

   These were referenced from four places and defined in none — an edit that
   was tidying the dawn deleted both keyframe blocks and left the animation
   names behind, so for weeks the cloud never came over and nobody could see
   why. A missing @keyframes is not an error: the animation simply resolves to
   nothing and the element sits at its static opacity.

   Two clouds per cycle, not one. The first is the short one, about a tenth of
   the way in; the second is longer and heavier and arrives around three
   fifths through. The sun layer is the exact inverse of the cloud layer at
   every stop, because the two things are one event — the beam is blocked, so
   the room loses its warmth and gains the sky's blue at the same instant. */
@keyframes cloudPass {
  0%,  6%  { opacity: 0; }
  10%      { opacity: calc(var(--wx-cool) * .62); }
  15%      { opacity: calc(var(--wx-cool) * .62); }
  21%      { opacity: 0; }
  59%      { opacity: 0; }
  68%      { opacity: var(--wx-cool); }
  80%      { opacity: var(--wx-cool); }
  92%, 100% { opacity: 0; }
}
@keyframes sunFade {
  0%,  6%  { opacity: var(--wx-sun); }
  10%      { opacity: calc(var(--wx-sun) * .42); }
  15%      { opacity: calc(var(--wx-sun) * .42); }
  21%      { opacity: var(--wx-sun); }
  59%      { opacity: var(--wx-sun); }
  68%      { opacity: calc(var(--wx-sun) * .15); }
  80%      { opacity: calc(var(--wx-sun) * .15); }
  92%, 100% { opacity: var(--wx-sun); }
}

/* ── a cloud goes over ─────────────────────────────────────────────────────
   Direct sunlight is about 4870K. Take the sun away and what is left is
   skylight at around 7000K, and open shade higher still — which is why a
   shadow on a clear day photographs blue, and why a room goes cold and flat
   the moment a cloud arrives. Two things happen at once and neither is
   optional: the light gets DIMMER because the direct beam is gone, and it
   gets BLUER because only the scattered sky is left lighting the room.

   So the warm layer fades most of the way out while a cool haze comes over
   everything. Plain alpha, not a blend mode — multiply was the obvious choice
   and it was wrong: multiplying blue over a warm wall gives mauve, not the
   blue-grey a cloud gives, because the result of a multiply is bound to the
   colour underneath it. Mixing toward a slate blue is not bound to anything,
   so it lands on the same cool grey whichever preset is behind it. It also
   flattens the contrast a little, which is the other thing overcast light
   does and the reason a room feels dull under it.

   154 seconds, with the cloud taking about a fifth of it: often enough to
   notice if you glance up, rare enough not to be a rhythm to work against.
   The two layers share one period so they stay exactly opposite. */
.skylight {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background: rgb(var(--wx-sky, 122, 146, 186));  /* the sky without the sun */
  opacity: 0;
}
body.glass.no-photo .skylight { display: none; }
body.glass.lit-room.light-moves .skylight {
  animation: cloudPass var(--wx-period) ease-in-out infinite;
}

/* ── the light arriving ────────────────────────────────────────────────────
   Played once, on signing in. Black, then the blue of a cloud sitting
   overhead, then the sun coming through to the warmth the loop runs at.

   It happens entirely BEHIND the interface. Both layers sit at z-index 0 and
   every panel is at 1, so the screen is usable from the first frame — you can
   type into the search bar through the dark. This is weather, not a splash
   screen, and it must never be something to wait out.

   The handover is done with a delay rather than with a listener. Both
   animations are declared on the element, the arrival first and the loop
   started at the moment the arrival ends; the later declaration wins the
   property as soon as it begins, so the loop simply takes over. Change the
   arrival's length and the two delays below have to move with it. */
/* 19 seconds: seven of dawn, ten held at first light, two to let go.

   The hold is the point. Sunrise colour does not arrive and immediately
   become daylight — the low, rose-coloured phase is the longest-looking part
   of a dawn, and cutting straight from it to the day's ordinary light threw
   away the best of what the climb had just made. */
/* The dawn is the sign-in, not the weather: it is a colour coming up over
   the whole screen and it belongs to every backdrop. What must NOT follow it
   on the others is the cloud loop, so the handover to cloudPass is declared
   separately and only for the studio. */
body.glass.light-arrive .skylight {
  animation: arriveSky 19s cubic-bezier(.32,.06,.28,1) 1 both;
}
body.glass.lit-room.light-arrive .skylight {
  animation: arriveSky 19s cubic-bezier(.32,.06,.28,1) 1 both,
             cloudPass var(--wx-period) ease-in-out 19s infinite;
}
body.glass.lit-room.light-arrive.light-moves::after {
  animation: leafDrift var(--wx-drift) ease-in-out infinite alternate,
             leafBreathe calc(var(--wx-drift) * 1.65) ease-in-out infinite alternate,
             leafTilt calc(var(--wx-drift) * 1.16) ease-in-out infinite alternate,
             arriveSun 19s cubic-bezier(.32,.06,.28,1) 1 both,
             sunFade var(--wx-period) ease-in-out 19s infinite;
}
/* With the movement switched off there is no drift to carry, but the arrival
   still runs — it is about the light, not about motion. */
body.glass.lit-room.light-arrive::after {
  animation: arriveSun 19s cubic-bezier(.32,.06,.28,1) 1 both;
}

/* Night, then blue hour, then the sun striking at an angle.

   That order is not a choice, it is what dawn does. With the sun well below
   the horizon there is no direct light at all, and what reaches the ground has
   been scattered so hard that the warm wavelengths are effectively gone —
   Rayleigh again, the same reason the daytime sky is blue. So the world comes
   back blue before it comes back warm, and only when the sun clears the
   horizon and starts striking things at an angle does the orange arrive.

   The easing is deliberately not symmetrical. cubic-bezier(.32,.06,.28,1)
   crawls at the start and opens out at the end, because that is how it feels
   to watch: nothing, nothing, still nothing, and then suddenly it is morning.
   A linear dawn reads as a slider being dragged. */
@keyframes arriveSky {
  0%   { background-color: #000; opacity: 1; }
  5%   { background-color: #000; opacity: 1; }               /* night holds */
  11%  { background-color: rgb(28, 44, 86); opacity: .96; }  /* the first blue */
  19%  { background-color: rgb(58, 88, 144); opacity: .84; }
  28%  { background-color: rgb(70, 100, 154); opacity: .74; }
  37%  { background-color: rgb(74, 105, 158); opacity: .68; } /* first light */
  89%  { background-color: rgb(74, 105, 158); opacity: .68; } /* and it stays */
  100% { background-color: rgb(122, 146, 186); opacity: 0; }  /* let go */
}

/* The lit surfaces first, and hard.

   This layer is masked to the photograph's own highlights, so raising it early
   lights exactly what the sun would light first and nothing else: the window
   itself, the strip of floor under it, the edge of the wall it falls across.
   It goes to more than twice the level the loop settles at — first light is
   the most directional light of the day, and low sun through a window is
   brighter on the surfaces it touches than noon ever is.

   Paired with the blue still sitting at .68, that is what makes the rose: a
   cool haze with a hot, directional light cutting through it. Neither layer
   is pink on its own. Move one of these numbers and the colour moves, so the
   two sets of keyframes below have to be read together. */
@keyframes arriveSun {
  0%, 8% { opacity: 0; }
  16%    { opacity: .30; }    /* the first surfaces catch it */
  26%    { opacity: .62; }
  37%    { opacity: .71; }    /* full and hot, low and directional */
  89%    { opacity: .71; }    /* and it stays */
  100%   { opacity: .34; }    /* down to where the loop lives */
}

/* ── leaving, and arriving ─────────────────────────────────────────────────
   Signing out is the same seven seconds run backwards: the light goes out of
   the room and leaves it black, which is where the sign-in screen already
   lives, so the two meet without a seam.

   The interface goes first, on two seconds of its own. It should not be the
   last thing on screen — a panel sitting on a black rectangle looks like a
   fault, where a photograph going dark looks like the end of the day. */
/* Dusk is an ANIMATION, not a transition, and that is the whole of the fix.

   It was written as a transition: drop the loop's animation and set the new
   values, and let the browser interpolate between them. It does not. Removing
   an animation and setting the property it was animating happens in one style
   recalculation, so there is no previous value for a transition to start from
   and the screen went black in a single frame. Seven seconds of sunset,
   delivered as a cut.

   An animation has no such problem: it declares both ends itself. This one is
   the arrival run backwards — the warmth goes first, then the blue comes up
   and deepens, then black. */
body.glass.light-leaving::before { animation: none; }
body.glass.light-leaving::after {
  animation: duskSun 7s ease-in 1 both;
}
body.glass.light-leaving .skylight {
  animation: duskSky 7s cubic-bezier(.4,0,.7,.5) 1 both;
}
@keyframes duskSun {
  0%   { opacity: .34; }
  38%  { opacity: .16; }
  100% { opacity: 0; }
}
@keyframes duskSky {
  0%   { background-color: rgb(122, 146, 186); opacity: 0; }
  34%  { background-color: rgb(74, 100, 150); opacity: .42; }
  66%  { background-color: rgb(30, 42, 74); opacity: .80; }
  100% { background-color: #000; opacity: 1; }
}

/* One after the other, not together. The sign-in card takes two seconds to
   go, and only then does the first screen begin its four — so there is a
   moment with nothing on the glass but the light coming up, and the interface
   arrives into a room rather than on top of the thing it is replacing.

   The delay is what sequences them, and `both` is what makes the delay mean
   anything: without a backwards fill the screen would sit at full opacity for
   those two seconds and then blink to nothing to start its fade. */
body.ui-arriving .shell,
body.ui-arriving .home-row { animation: uiArrive 4s ease-out 2s 1 both; }
body.ui-leaving .shell,
body.ui-leaving .home-row { animation: uiLeave 2s ease-in 1 both; }
@keyframes uiArrive { from { opacity: 0; } to { opacity: 1; } }
@keyframes uiLeave  { from { opacity: 1; } to { opacity: 0; } }

/* The sign-in screen used to be black — the bottom of the curve the arrival
   then climbed. Daytona's call is that it shows the room instead, at the top
   of all three dials: warmest light, fastest cloud, most movement. So the
   photograph and both wash layers stay exactly as they are on every other
   screen, and there is nothing here to black them out.

   The weather values are forced in applyWeather() rather than here, because
   they are numbers the JS already owns and a second set of them in CSS is a
   second set to keep in step.

   What goes with the black is the DAWN. A seven-second climb out of darkness
   only means something if you were in darkness; played from an already-lit
   room it is a dip and a recovery, which is worse than no transition at all.
   Signing in now just brings the panels across. */
.login.overlay.leaving { animation: cardLeave 2s ease-in 1 both; }
@keyframes cardLeave { from { opacity: 1; } to { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  /* `lit-room` is carried here as well, and it has to be. The rules that
     START these animations gained that class, so a two-class selector no
     longer out-specifies them — leave it off and "stop animating things"
     quietly stops being obeyed. Same count of classes, later in the file,
     so it wins. */
  body.glass.lit-room.light-moves::before,
  body.glass.lit-room.light-moves::after,
  body.glass.lit-room.light-moves .skylight,
  body.glass.lit-room.light-arrive::after,
  body.glass.lit-room.light-arrive.light-moves::after,
  body.glass.lit-room.light-arrive .skylight,
  body.glass.light-arrive .skylight { animation: none; }
}
body.glass .shell, body.glass .home-row { position: relative; z-index: 1; }
body.glass .home2 { background: transparent; color: #f4f3f0; }

/* ── the sidebar is chrome, so it stays opaque ───────────────────────── */
/* Same fill and blur as the top bar, so the two read as one piece of chrome
   wrapping the page rather than two different greys meeting at a corner. */
body.glass .sidebar {
  /* Grey Nickel from the palette, at the .72 the warm dark was carried at —
     the opacity is what makes it a bar rather than a slab, so only the
     colour moved. Everything written on it had to flip with it; see the
     block headed "the light rail" further down. */
  background: rgba(195, 195, 189, .72);
  -webkit-backdrop-filter: blur(22px) saturate(1.1);
          backdrop-filter: blur(22px) saturate(1.1);
  border-right: 1px solid rgba(255,255,255,.10);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14);
  /* Set the inherited colour here, at the container.

     Anything inside the rail that does not state its own colour otherwise
     resolves the ROOT --color-text token — the light theme's charcoal — and
     goes near-black on a dark rail. The nav items and the ghost buttons state
     theirs, which is why they looked fine; the user chip did not, and both its
     lines were charcoal, the name only reading as lighter because of the pale
     chip behind it. Stating it once here fixes what is in the rail today and
     whatever gets added to it later. */
  color: #f4f3f0;
}
body.glass .userchip-role { color: rgba(244,243,240,.62); }
body.glass .wordmark { color: #f4f3f0; }
body.glass .wordmark small { color: rgba(244,243,240,.55); }
body.glass .nav-item { color: rgba(244,243,240,.82); }
body.glass .nav-item:hover { background: rgba(255,255,255,.10); color: #fff; }
body.glass .nav-item.active { background: rgba(255,255,255,.18); color: #fff; }
/* A shade under the parent item, so the submenu reads as subordinate to it
   without dropping out of contrast the way it did when it was inherited. */
body.glass .nav-subitem { color: rgba(244,243,240,.72); }
body.glass .nav-subitem:hover { background: rgba(255,255,255,.10); color: #fff; }
body.glass .nav-subitem.active { background: rgba(255,255,255,.18); color: #fff; }
body.glass .userchip { background: rgba(255,255,255,.08); }
body.glass .avatar { background: rgba(255,255,255,.22); color: #fff; }
body.glass .btn-ghost { color: rgba(244,243,240,.82); }
body.glass .btn-ghost:hover { background: rgba(255,255,255,.12); color: #fff; }

/* ── panels ──────────────────────────────────────────────────────────── */
body.glass .panel,
body.glass .setblock,
body.glass .docrow,
body.glass .entry-body,
body.glass .clientbar,
body.glass .stat,
body.glass .empty,
body.glass .quiet-row,
body.glass .split-list {
  background: var(--glass);
  -webkit-backdrop-filter: blur(20px) saturate(1.1);
          backdrop-filter: blur(20px) saturate(1.1);
  border: 1px solid var(--hair);
  box-shadow: var(--lift), 0 6px 24px rgba(20,18,16,.20);
}
body.glass .table-wrap {
  background: var(--glass-strong);   /* opaque: no blur needed, and cheaper */
  border: 1px solid var(--hair);
  box-shadow: var(--lift), 0 10px 34px rgba(20,18,16,.34);
}
body.glass .table th {
  background: transparent; color: rgba(244,243,240,.62);
  border-bottom-color: var(--hair);
}
body.glass .table td { border-bottom-color: rgba(255,255,255,.08); }
body.glass .table tbody tr:hover { background: rgba(255,255,255,.07); }
body.glass .rowlink, body.glass .linkish { color: #f0d9c6; }

/* ── controls ────────────────────────────────────────────────────────── */
/* Controls that sit ON a panel take a fixed dark fill and no blur — blurring
   the photograph behind an opaque-ish panel makes a control's brightness
   depend on whatever leaf happens to be behind it, so the same select reads
   near-white in one place and near-black in another. */
body.glass .input, body.glass .select, body.glass textarea.input {
  background: rgba(20, 18, 16, .42);
  border-color: rgba(255,255,255,.22);
  color: #f4f3f0; caret-color: #f4f3f0;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08);
}
/* The two that sit directly on the photograph keep the frosted treatment. */
body.glass .bigsearch2 { --bar-ink: #f4f3f0; }
body.glass .bigsearch2 textarea, body.glass .ov-search input {
  background: rgba(28, 26, 23, .52);
  border-color: rgba(255,255,255,.24);
  color: #f4f3f0; caret-color: #f4f3f0;
  -webkit-backdrop-filter: blur(18px) saturate(1.1);
          backdrop-filter: blur(18px) saturate(1.1);
  box-shadow: var(--lift), 0 4px 18px rgba(16,14,12,.3);
}
/* AFTER the rule above, not before it. Same specificity, so order is the only
   thing deciding, and losing meant the input painted its own text underneath
   the mirror's. Identical text on top of itself just looks faintly bold — until
   the line overflows, the input scrolls and the mirror does not, and the two
   copies slide apart into a blur. */
body.glass .bigsearch2 textarea { color: transparent; }
body.glass .input::placeholder, body.glass .bigsearch2 textarea::placeholder,
body.glass .ov-search input::placeholder { color: rgba(244,243,240,.52); }
body.glass .input:focus-visible, body.glass .select:focus-visible,
body.glass .bigsearch2 textarea:focus-visible, body.glass .ov-search input:focus-visible {
  border-color: rgba(255,255,255,.46); background: rgba(20,18,16,.58);
}
/* The `background` shorthand above resets size, position and repeat, so the
   arrow gradients tiled across the whole control and painted it white.
   Re-state all four. */
body.glass .select {
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-size: 5px 5px, 5px 5px;
  background-position: calc(100% - 17px) 15px, calc(100% - 12px) 15px;
  background-repeat: no-repeat;
  background-color: rgba(20, 18, 16, .42);
}
body.glass .btn-primary {
  background: rgba(255,255,255,.20); color: #fff;
  border: 1px solid rgba(255,255,255,.24); box-shadow: var(--lift);
}
body.glass .btn-primary:hover:not(:disabled) { background: rgba(255,255,255,.30); }
body.glass .btn-secondary { border-color: rgba(255,255,255,.22); color: #f4f3f0; }
body.glass .btn-secondary:hover:not(:disabled) { background: rgba(255,255,255,.12); }
body.glass .switch { background: rgba(255,255,255,.14); border-color: rgba(255,255,255,.22); }
body.glass .switch.on { background: rgba(255,255,255,.55); border-color: rgba(255,255,255,.6); }
body.glass .switch::after { background: #2f2d2a; }
body.glass .switch.on::after { background: #fff; }

/* ── chips and small type ────────────────────────────────────────────── */
body.glass .tag { background: rgba(255,255,255,.14); color: rgba(244,243,240,.92); }
body.glass .tag-treating, body.glass .tag-settled          { background: rgba(150,196,130,.26); color: #e6f3dc; }
body.glass .tag-negotiating, body.glass .tag-demand-sent   { background: rgba(226,140,86,.30);  color: #ffe4d0; }
body.glass .tag-records-requested, body.glass .tag-records-received { background: rgba(226,178,86,.26); color: #ffeed2; }
body.glass .tag-litigation { background: rgba(214,110,100,.30); color: #ffdad5; }
body.glass .tag-intake, body.glass .tag-closed { background: rgba(255,255,255,.12); color: rgba(244,243,240,.88); }
body.glass .lock-pill { background: rgba(255,255,255,.14); color: rgba(244,243,240,.86); }
body.glass .correction-banner { background: rgba(214,110,100,.22); color: #ffdad5; }
body.glass .hint, body.glass .muted, body.glass .ov-sub { color: rgba(244,243,240,.62); }
body.glass .hint b { color: #ffcfc4; }
body.glass .kbd { background: rgba(255,255,255,.14); border-color: rgba(255,255,255,.2); color: rgba(244,243,240,.8); }
body.glass .type-icon { background: rgba(255,255,255,.14); color: #f4f3f0; }
body.glass .typebtn { border-color: rgba(255,255,255,.20); color: rgba(244,243,240,.88); }
body.glass .typebtn:hover { background: rgba(255,255,255,.12); }
body.glass .typebtn.on { background: rgba(255,255,255,.85); color: #2b2926; border-color: transparent; }
body.glass .client-pill { color: rgba(244,243,240,.9); }
body.glass .client-pill:hover { background: rgba(255,255,255,.10); }
body.glass .client-pill.on { background: rgba(255,255,255,.86); border-color: transparent; }
body.glass .client-pill.on .cp-name, body.glass .client-pill.on .cp-case { color: #2b2926; }

/* ── floating surfaces ───────────────────────────────────────────────── */
body.glass .acbox2, body.glass .histmenu, body.glass .dialog, body.glass .suggest {
  background: rgba(52,50,46,.88); background-clip: padding-box;
  -webkit-backdrop-filter: blur(26px) saturate(1.1);
          backdrop-filter: blur(26px) saturate(1.1);
  border: 1px solid var(--hair);
  box-shadow: var(--lift), 0 14px 40px rgba(16,14,12,.45);
}
body.glass .ac-item, body.glass .hist-item { color: #f4f3f0; }
body.glass .ac-item:hover, body.glass .ac-item.on,
body.glass .hist-item:hover { background: rgba(255,255,255,.12); }
body.glass .ac-item .cn, body.glass .hist-item .when, body.glass .ac-head { color: rgba(244,243,240,.58); }
body.glass .fan-label, body.glass .fan-btn, body.glass .home2-btn, body.glass .histbtn {
  background: rgba(62,59,55,.72); color: #f4f3f0;
  border-color: rgba(255,255,255,.20);
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  box-shadow: var(--lift), 0 4px 14px rgba(16,14,12,.3);
}
body.glass .fan-item:hover .fan-btn { background: rgba(255,255,255,.9); color: #2b2926; }
body.glass .home2-bar {
  background: rgba(195, 195, 189, .72);
  -webkit-backdrop-filter: blur(22px) saturate(1.1);
          backdrop-filter: blur(22px) saturate(1.1);
  border-bottom-color: var(--hair);
  box-shadow: var(--lift);
}
body.glass .home2-brand, body.glass .home2-who { color: #f4f3f0; }
body.glass .home2-meta { color: rgba(244,243,240,.66); }
body.glass .home2-who .avatar { background: rgba(255,255,255,.22); color: #fff; }

/* ── the object scenes ───────────────────────────────────────────────── */
/* These labels sit directly on the photograph rather than on a panel, so they
   carry their own shadow instead of a chip — chrome here would clutter the
   scene the objects are meant to own. */
body.glass .picker2 .pick-label, body.glass .ov-name {
  color: #fbfaf8;
  text-shadow: 0 1px 3px rgba(14,12,10,.95), 0 0 16px rgba(14,12,10,.8);
}
/* The light skin darkens the label on hover, which reads as a black word on a
   photograph here. Beat that rule (0,4,0) on specificity and hold the white. */
body.glass .picker2 .pick-wrap:hover .pick-label,
body.glass .picker2 .pick:focus-visible .pick-label { color: #fbfaf8; }
body.glass .ov-count {
  color: #fff;
  text-shadow: 0 2px 4px rgba(14,12,10,.95), 0 0 20px rgba(14,12,10,.8);
}
body.glass .ov-sub, body.glass .ov-legend { text-shadow: 0 1px 3px rgba(14,12,10,.9); }
body.glass .picker2 .pick-arc::after { border-color: rgba(255,255,255,.85); }
body.glass .obj { filter: drop-shadow(0 8px 18px rgba(16,14,12,.45)); }
body.glass .studio::before, body.glass .ov-stage::after { opacity: .28; }
body.glass .ov-legend { color: rgba(244,243,240,.66); }
body.glass .section-title, body.glass h2, body.glass h3 { color: #f4f3f0; }
body.glass .tlf-field > span, body.glass .tlf-count { color: rgba(244,243,240,.72); }
body.glass .demo-ribbon { background: rgba(150,74,26,.9); }
body.glass #cursor {
  color: #f6f5f2; background: currentColor;
  /* light fill, dark halo — the pale crosshair still has an edge on the white
     composer field and the white suggestion list */
  filter: drop-shadow(0 0 1.2px rgba(16,14,12,.95))
          drop-shadow(0 0 3px rgba(16,14,12,.55))
          drop-shadow(0 1px 2px rgba(16,14,12,.4));
}
body.glass #cursor.tap { background: transparent; }
body.glass #cursor.text { color: #f4f3f0; background: currentColor; }
body.glass .bd-preview { border-color: var(--hair); }

/* ── sign-in, in the glass scheme ────────────────────────────────────── */
body.glass .login { background: transparent; position: relative; z-index: 1; color: #f4f3f0; }
body.glass .login-title { color: #ffffff; text-shadow: 0 2px 6px rgba(14,12,10,.85), 0 0 26px rgba(14,12,10,.7); }
body.glass .login-tag   { color: rgba(255,255,255,.88); text-shadow: 0 1px 4px rgba(14,12,10,.85); }
body.glass .login-card {
  background: rgba(48, 46, 42, .82);
  -webkit-backdrop-filter: blur(26px) saturate(1.1);
          backdrop-filter: blur(26px) saturate(1.1);
  border: 1px solid rgba(255,255,255,.16);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18), 0 18px 46px rgba(14,12,10,.45);
  color: #f4f3f0;
}
body.glass .login-card .field > label { color: rgba(244,243,240,.72); }
body.glass .login-foot { color: rgba(244,243,240,.62); }
body.glass .or-rule { color: rgba(244,243,240,.62); }
body.glass .or-rule::before, body.glass .or-rule::after { background: rgba(255,255,255,.20); }
body.glass .login-error { background: rgba(214,110,100,.24); color: #ffdad5; }

/* ── surfaces that live outside the shell ─────────────────────────────
   Dialogs and toasts are appended to <body>, so they inherit `color` from
   body — which resolves the ROOT token, not the shell's. They need stating
   explicitly or they render dark-on-dark. */
body.glass .dialog { color: #f4f3f0; }
body.glass .dialog h3 { color: #ffffff; }
body.glass .dialog p { color: rgba(244,243,240,.82); }
body.glass .dialog p b { color: #ffffff; }
body.glass .dialog .field > label { color: rgba(244,243,240,.72); }
body.glass .dialog .input, body.glass .dialog .select {
  background: rgba(20,18,16,.5); border-color: rgba(255,255,255,.22); color: #f4f3f0;
}
body.glass .dialog .quiet-row { color: #f4f3f0; }
body.glass .dialog .hint { color: rgba(244,243,240,.62); }
body.glass .backdrop { background: rgba(10, 9, 8, .62); }
body.glass .toast {
  background: rgba(30, 28, 25, .94); color: #f4f3f0;
  border: 1px solid rgba(255,255,255,.16);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 10px 30px rgba(10,9,8,.5);
}

/* ── page frame ───────────────────────────────────────────────────────
   The demo ribbon's height depends on how its text wraps, so the shell can
   no longer assume 30px for it — that assumption left a strip of background
   above the sidebar at some widths, and overflowed the page at others.
   Let flex measure it instead. */
#app { display: flex; flex-direction: column; height: 100vh; }
/* Positioned, so the fixed photo layer (z-index 0) cannot paint over it —
   an unpositioned ribbon rendered as a mysterious strip of photograph above
   the top bar. Outside demo mode there is no ribbon and the bar sits flush
   against the top of the window. */
.demo-ribbon { flex: 0 0 auto; position: relative; z-index: 2; }
.shell, .shell.has-ribbon { flex: 1 1 auto; height: auto; min-height: 0; }
/* The search screen sits beside the rail, so the two share a row and the
   screen itself is just a flex child of it. */
.home-row, .home-row.has-ribbon {
  display: flex; flex: 1 1 auto; min-height: 0;
}
.home2, .home2.has-ribbon {
  position: relative; inset: auto; top: auto;
  flex: 1 1 auto; min-width: 0; min-height: 0;
}
.home-row .sidebar { flex-shrink: 0; }
.login { flex: 1 1 auto; min-height: 0; }

/* ═══════════════════════════════════════════════════════════════════════
   SIDEBAR RAIL
   The sidebar collapses to a narrow rail. Nav items carry an icon so the
   collapsed state is still navigable rather than merely tidy, and every
   control keeps a title so hovering names it.
   ═══════════════════════════════════════════════════════════════════════ */

.sidebar { position: relative; transition: width .22s cubic-bezier(.2,.8,.25,1); }

/* In normal flow rather than overhanging the edge — the sidebar scrolls, so
   anything hanging outside it gets clipped. */
.rail-toggle {
  align-self: flex-end; flex: 0 0 auto; margin-bottom: calc(var(--space-4) * -1);
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--color-divider); background: var(--color-neutral-100);
  color: var(--color-neutral-700); font-size: 13px; line-height: 1;
  padding: 0; transition: background .15s, color .15s, transform .15s;
}
.rail-toggle:hover { transform: scale(1.12); }

.nav-item { gap: 10px; }
.ni-ico { display: grid; place-items: center; width: 18px; height: 18px; flex-shrink: 0; }
.ni-ico svg { width: 17px; height: 17px; stroke: currentColor; fill: none;
              stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.ni-label { white-space: nowrap; overflow: hidden; }
.wordmark .wm-mini { display: none; }
.foot-btn { display: flex; align-items: center; gap: 10px; justify-content: flex-start; }
/* The guide is the only foot button that leads to a SCREEN rather than
   performing an action, so it is the only one that can be the screen you are
   already on — and it has to say so, the way the nav items do. */
.foot-btn.active { background: var(--color-neutral-200); color: var(--color-neutral-900); }
.fb-ico { display: grid; place-items: center; width: 18px; height: 18px; flex-shrink: 0; }
.fb-ico svg { width: 16px; height: 16px; stroke: currentColor; fill: none;
              stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* ── collapsed ───────────────────────────────────────────────────────── */
.sidebar.mini { width: 66px; padding-left: var(--space-2); padding-right: var(--space-2); }
.sidebar.mini .rail-toggle { align-self: center; }
.sidebar.mini .wm-full { display: none; }
.sidebar.mini .wordmark {
  display: block; text-align: center; padding: 0;
  font-family: var(--font-display); font-size: 26px;
}
.sidebar.mini .wordmark .wm-mini { display: block; }
.sidebar.mini .nav-item { justify-content: center; padding-left: 0; padding-right: 0; }
.sidebar.mini .ni-label { display: none; }
.sidebar.mini .userchip { justify-content: center; padding: var(--space-2) 0; }
.sidebar.mini .uc-text { display: none; }
.sidebar.mini .foot-btn { justify-content: center; padding-left: 0; padding-right: 0; }
.sidebar.mini .fb-label { display: none; }

/* Collapsed, these three lose their labels and become their 18px icon plus
   padding — 30x30 on a phone, which is the smallest thing in the whole
   application and includes Sign out.

   Measured on a 375x812 viewport: every other control is at least 33px tall
   and wide enough to hit, so these were the only three small in BOTH
   directions. Same reasoning as the document toolbar above: a finger is not
   a mouse pointer, and 44px is the floor every mobile guideline agrees on. */
@media (pointer: coarse) {
  .sidebar.mini .foot-btn { min-width: 44px; min-height: 44px; }
}

@media (max-width: 860px) {
  /* The narrow layout turns the sidebar into a full-width strip, which was
     written for .shell — and .shell switches to a column to receive it. The
     search screen's own row never got the same treatment, so a 100%-wide
     sidebar sat in a flex ROW and took the entire window, squeezing the
     search bar to nothing and pushing the top bar off the screen. */
  .home-row, .home-row.has-ribbon { flex-direction: column; }
  .home-row .sidebar { width: 100%; flex: 0 0 auto; }

  .rail-toggle { display: none; }
  .sidebar.mini { width: 100%; }
  .sidebar.mini .ni-label, .sidebar.mini .wm-full { display: inline; }
  /* Matching `.sidebar.mini .wordmark .wm-mini` above, class for class. A
     media query does not outrank anything — it only decides whether the rule
     is considered — so at two classes this lost to the three-class rule it
     was written to undo, and the strip carried the full wordmark AND the
     single letter that stands in for it, stacked. */
  .sidebar.mini .wordmark .wm-mini { display: none; }
  /* And the collapsed wordmark stops being centred, because in a strip it is
     no longer the width of the rail — it was centring across the window. */
  .sidebar.mini .wordmark { display: inline-block; text-align: left; }
}
@media (prefers-reduced-motion: reduce) { .sidebar { transition: none; } }

/* glass treatment for the new control */
body.glass .rail-toggle {
  background: #4a4743; border-color: rgba(255,255,255,.22); color: #f4f3f0;
  box-shadow: 0 2px 8px rgba(14,12,10,.4);
}
body.glass .rail-toggle:hover { background: #5a5651; }

/* ═══════════════════════════════════════════════════════════════════════
   SEARCH PICKER — line icons
   The 3D solids read as toys next to the rest of the interface, which is
   stroked line icons on glass. Same shape language as the sidebar and the
   action fan, at a size that does not dominate the screen.
   ═══════════════════════════════════════════════════════════════════════ */

.picker2 .picker-row { gap: var(--space-6); }
.picker2 .pick { gap: 12px; padding: 0; }

.pick-btn {
  display: grid; place-items: center;
  width: 58px; height: 58px; border-radius: 50%;
  background: rgba(62, 59, 55, .70);
  border: 1px solid rgba(255,255,255,.20);
  -webkit-backdrop-filter: blur(18px) saturate(1.1);
          backdrop-filter: blur(18px) saturate(1.1);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.20), 0 6px 20px rgba(14,12,10,.34);
  color: #f4f3f0;
  transition: transform .2s cubic-bezier(.2,.8,.25,1), background .18s, color .18s, box-shadow .2s;
}
.pick-btn svg {
  width: 24px; height: 24px; stroke: currentColor; fill: none;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.pick-wrap:hover .pick-btn, .pick:focus-visible .pick-btn {
  transform: translateY(-5px) scale(1.07);
  background: rgba(255,255,255,.92); color: #24221f;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 12px 28px rgba(14,12,10,.4);
}
.picker2 .pick-label { font-size: 14.5px; }

/* the fan starts just above the smaller button */
.fan { bottom: 62%; padding-bottom: 26px; }

@media (prefers-reduced-motion: reduce) {
  .pick-btn, .pick-wrap:hover .pick-btn { transition: none; transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   RADIAL MENU
   Hovering a picker button opens its actions around it rather than stacking
   them above it. Three actions fan upward, because these buttons sit near the
   bottom of the window and a full ring would run off the edge.

   The hub keeps its place: the ring grows out of the button rather than
   replacing it, so the thing you aimed at is still under the cursor.
   ═══════════════════════════════════════════════════════════════════════ */

/* Restored in full: this rule was lost with the old fan styles. Without
   `position` the radial anchored to the page; without the centring the button
   sat left of its column while the radial's origin stayed at the column's
   centre, so the menu opened beside the button rather than around it. */
.pick-wrap {
  position: relative;
  display: flex; flex-direction: column; align-items: center;
}

/* While one button is open the others recede to half opacity — present enough
   to keep the row's shape, quiet enough that a wide arc reads over them. They
   stay clickable, so moving straight to another button still works. */
.picker-row:has(.pick-wrap:hover) .pick-wrap:not(:hover),
.picker-row:has(.pick-wrap:focus-within) .pick-wrap:not(:focus-within) {
  opacity: .5;
}
.pick-wrap { transition: opacity .22s ease; }

.radial {
  position: absolute; left: 50%; top: 29px;   /* the button's centre */
  width: 0; height: 0; z-index: 6;
  pointer-events: none;
}

/* An invisible disc that keeps the whole arc inside one hover region, so
   travelling between items never crosses dead space. */
.radial-pad {
  position: absolute; left: 0; top: 0;
  width: 300px; height: 300px; border-radius: 50%;
  transform: translate(-50%, -50%);
  opacity: 0; pointer-events: none;
}
.pick-wrap:hover .radial-pad, .pick-wrap:focus-within .radial-pad,
.dial-wrap:hover .radial-pad, .dial-wrap:focus-within .radial-pad { pointer-events: auto; }

/* the faint dial the items sit on */
.radial-ring {
  position: absolute; left: 0; top: 0;
  width: 252px; height: 252px; border-radius: 50%;
  transform: translate(-50%, -50%) scale(.55);
  background:
    radial-gradient(closest-side, transparent 58%, rgba(255,255,255,.10) 59%, rgba(255,255,255,.10) 96%, transparent 97%);
  opacity: 0;
  transition: opacity .26s, transform .34s cubic-bezier(.2,.9,.28,1.25);
}
.pick-wrap:hover .radial-ring, .pick-wrap:focus-within .radial-ring,
.dial-wrap:hover .radial-ring, .dial-wrap:focus-within .radial-ring {
  opacity: 1; transform: translate(-50%, -50%) scale(1);
}

.radial-item {
  position: absolute; left: 0; top: 0;
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  width: 88px; margin: 0; padding: 0; border: none; background: none;
  cursor: pointer; font: inherit; color: #f4f3f0;
  /* rotate out to the angle, push along the radius, then rotate back so the
     label stays upright */
  transform: translate(-50%, -50%) rotate(var(--a)) translateY(-24px) rotate(calc(var(--a) * -1));
  opacity: 0; pointer-events: none;
  transition: transform .34s cubic-bezier(.2,.9,.28,1.25), opacity .2s;
  transition-delay: calc(var(--i) * 40ms);
}
.pick-wrap:hover .radial-item, .pick-wrap:focus-within .radial-item,
.dial-wrap:hover .radial-item, .dial-wrap:focus-within .radial-item {
  opacity: 1; pointer-events: auto;
  transform: translate(-50%, -50%) rotate(var(--a)) translateY(-126px) rotate(calc(var(--a) * -1));
}

.ri-btn {
  display: grid; place-items: center;
  width: 46px; height: 46px; border-radius: 50%;
  background: rgba(60, 57, 53, .82);
  border: 1px solid rgba(255,255,255,.20);
  -webkit-backdrop-filter: blur(16px) saturate(1.1);
          backdrop-filter: blur(16px) saturate(1.1);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.20), 0 5px 16px rgba(14,12,10,.4);
  color: #f4f3f0;
  transition: transform .16s, background .16s, color .16s, box-shadow .16s;
}
.ri-btn svg {
  width: 20px; height: 20px; stroke: currentColor; fill: none;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
.ri-label {
  font-size: 12.5px; font-weight: 600; line-height: 1.25; text-align: center;
  color: #f0efec; text-shadow: 0 1px 3px rgba(14,12,10,.95), 0 0 12px rgba(14,12,10,.8);
  transition: color .16s;
}
.radial-item:hover .ri-btn, .radial-item:focus-visible .ri-btn {
  transform: scale(1.14);
  background: rgba(255,255,255,.94); color: #24221f;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 8px 22px rgba(14,12,10,.45);
}
.radial-item:hover .ri-label { color: #ffffff; }
.radial-item:focus-visible { outline: none; }

@media (prefers-reduced-motion: reduce) {
  .radial-item, .radial-ring { transition: opacity .15s; }
  .pick-wrap:hover .radial-item {
    transform: translate(-50%, -50%) rotate(var(--a)) translateY(-126px) rotate(calc(var(--a) * -1));
  }
}

/* ── signing in ───────────────────────────────────────────────────────
   The destination renders at full opacity underneath, and the sign-in screen
   floats over it and dissolves. Only the departing screen is animated —
   fading the arriving one would make its backdrop-filtered chrome shift
   colour the moment the fade finished. */
/* The old full-screen crossfade is gone — the grid is the transition now. What
   is left is the panel's own short exit, riding over the top of the grid rather
   than inside it. Giving the panel a hundred cloned tiles of its own to break
   up with was what made the whole thing stutter, and on the way back in it
   arrived twice: built by pixels, then fading in again over itself. */
.login.overlay { transition: opacity .3s ease, transform .3s ease; }
.login.overlay.leaving { opacity: 0; transform: scale(.985); }
/* Specific enough to beat `body.glass .login`, which sets position:relative —
   without this the departing card was appended to the page in normal flow
   instead of floating over the new screen, and the fade never read. */
.login.overlay,
body.glass .login.overlay {
  position: fixed; inset: 0; z-index: 150; pointer-events: none;
  display: flex; align-items: center; justify-content: center;
}

/* Moving the card out of #app re-inserts the element, which restarts any CSS
   animation on it — so the sign-in card replayed its entrance and appeared to
   fade in before fading out. Kill the entrance on the departing copy. */
.login.overlay .login-inner { animation: none !important; }

@media (prefers-reduced-motion: reduce) {
  .login { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   PIXELSWAP
   The sign-in screen gives way to the first screen through a grid of small
   windows, each one showing its own copy of the destination held at the same
   origin. See public/pixelswap.js for the geometry.
   ═══════════════════════════════════════════════════════════════════════ */

/* Between the destination and the departing sign-in card (z-index 150). The
   grid is an opaque cover over the finished screen, and the card sits on top
   of it whole — so the card fades as one piece instead of breaking up, and
   every square that shrinks away uncovers a square of the destination. */
.pixel-swap__grid {
  position: fixed; inset: 0; z-index: 120; pointer-events: none;
}
.pixel-swap__pixel {
  position: absolute; overflow: hidden; opacity: 0; contain: paint;
}
.pixel-swap__pixel-content { position: absolute; }
/* the panel's windows sit over the full-screen cover, where the panel was */
.pixel-swap__grid--panel { z-index: 160; }
.pixel-swap__canvas { position: absolute; left: 0; top: 0; }

/* Only used by `source` mode, which Donellus's sign-in no longer takes: cloning
   the app into every window cost seconds of layout. Kept because the mode is
   still the right one for a small scene, and the geometry is the component's.

   The scene each pixel is a window onto: the photograph, then a copy of the
   app, both at viewport coordinates so a pixel at (left, top) shows exactly
   what is at (left, top) on the real page. */
.pixel-swap__scene { position: absolute; left: 0; top: 0; }
.pixel-swap__app {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; height: 100%;
}
/* body.glass::before is fixed to the viewport and therefore outside every
   pixel's paint containment. Each scene carries its own copy. */
.pixel-swap__backdrop {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(38, 36, 33, .44), rgba(38, 36, 33, .44)),
    var(--backdrop-img, none);
  background-size: cover; background-position: center;
  background-color: #6d675e;
  /* The saturate(.72) that used to live here is baked into the files — see
     scripts/bake-saturation.py. It was free on this one element and ruinous on
     the sign-in grid, which puts the same photograph on 228 windows. */
}
body.no-photo .pixel-swap__backdrop { background-image: none; }

/* Two things have to be switched off inside a clone.

   A live backdrop-filter inside a clipped, transformed pixel samples that
   pixel's own empty backdrop rather than the page behind it, so the chrome
   would darken through the transition and snap straight the moment the real
   screen took over — the same fault that ruled out fading the destination.
   The scene carries the photograph instead and the panels composite over it.

   And every entry animation in the app would otherwise replay in all 200
   copies at once, each on its own clock. */
.pixel-swap__scene, .pixel-swap__scene * {
  -webkit-backdrop-filter: none !important;
          backdrop-filter: none !important;
}
.pixel-swap__scene *,
.pixel-swap__scene *::before,
.pixel-swap__scene *::after {
  animation: none !important;
  transition: none !important;
  caret-color: transparent !important;
}

/* ═══════════════════════════════════════════════════════════════════════
   REPORT BUILDER
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the sidebar submenu ───────────────────────────────────────────────
   A list rather than a hover flyout. These three are screens you work in,
   so which one you are on has to stay on screen while you are on it. */
.nav-item.has-sub { position: relative; }
.ni-chev {
  margin-left: auto; opacity: .5; font-size: 15px; line-height: 1;
  transition: opacity .15s;
}
.nav-item:hover .ni-chev { opacity: .9; }
.sidebar.mini .ni-chev { display: none; }

.nav-sub { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 6px; }
/* Collapsed, the labels are gone and a submenu would be three unlabelled
   icons under another icon. The screen's own tabs cover it. */
.sidebar.mini .nav-sub { display: none; }

/* Colour stated, never inherited. `color: inherit` here resolved all the way up
   to <body>, which resolves the ROOT --color-text token rather than the skin's —
   so these rendered the light theme's charcoal on a dark sidebar and could not
   be read. The same trap the table cells fell into. .nav-item above states its
   colour for exactly this reason; these have to as well.

   The dimming is in the colour rather than in opacity, so one rule sets the
   contrast instead of two multiplying into something nobody chose. */
.nav-subitem {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px 7px 30px; border-radius: 9px;
  font: inherit; font-size: 13.5px; text-align: left;
  color: var(--color-neutral-800); background: none; border: 0; cursor: pointer;
  transition: background .14s, color .14s;
}
.nav-subitem:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.nav-subitem.active { background: var(--color-accent); color: #fbeede; font-weight: 600; }
.nav-subitem .ni-ico { width: 16px; height: 16px; flex: 0 0 16px; }
.nav-subitem .ni-ico svg { width: 16px; height: 16px; }

/* ── the screens ───────────────────────────────────────────────────────── */
.rep-head { margin-bottom: var(--space-4); }
.rep-sub { font-size: 13.5px; color: var(--color-text-dim); max-width: 62ch; margin-top: 4px; }

.rep-tabs {
  display: flex; gap: 6px; margin-bottom: var(--space-5);
  border-bottom: 1px solid var(--color-divider); padding-bottom: 0;
}
.rep-tab {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 14px; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 14.5px; color: var(--color-text-dim);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color .14s, border-color .14s;
}
.rep-tab svg {
  width: 16px; height: 16px; fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.rep-tab:hover { color: var(--color-text); }
.rep-tab.on { color: var(--color-text); border-bottom-color: currentColor; font-weight: 600; }

.rep-form { display: flex; flex-direction: column; gap: var(--space-4); }
.rep-grid {
  display: grid; gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.rep-field { display: flex; flex-direction: column; gap: 4px; }
.rep-field > span {
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--color-text-dim);
}

.rep-quick, .rep-cols { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rep-quick-label {
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--color-text-dim); margin-right: 2px;
}

/* Column toggles. Pressed is a state, not a hover, so it has to read as one
   without colour alone carrying it — the weight changes too. */
.chip {
  display: inline-flex; align-items: center; padding: 5px 11px;
  border-radius: 999px; border: 1px solid var(--color-divider);
  background: none; color: var(--color-text-dim);
  font: inherit; font-size: 13px; cursor: pointer;
  transition: background .14s, color .14s, border-color .14s;
}
.chip:hover { color: var(--color-text); border-color: var(--color-text-dim); }
.chip.on {
  background: var(--color-text); border-color: var(--color-text);
  color: var(--color-neutral-100); font-weight: 600;
}

.rep-count {
  font-size: 13px; color: var(--color-text-dim); margin-bottom: var(--space-3);
}
.rep-result { margin-top: var(--space-5); }
.rep-table td { vertical-align: top; }
.rep-table th.wide, .rep-table td.wide { width: 45%; min-width: 22ch; }
.rep-table td.wide { white-space: normal; line-height: 1.5; }

.rep-layouts { display: flex; flex-direction: column; gap: var(--space-4); }
.rep-layout { display: flex; flex-direction: column; gap: var(--space-3); }
.rl-head { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.rl-head h3 { margin: 0; font-size: 17px; }
.rl-actions { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; }
.rl-meta { font-size: 13px; color: var(--color-text-dim); }
.rl-cols { display: flex; gap: 6px; flex-wrap: wrap; }
.rl-cols .chip { cursor: default; }

.rep-export { display: flex; flex-direction: column; gap: var(--space-4); }

/* A button that reads as a link, for pointing at another tab mid-sentence. */
.linkish {
  border: 0; background: none; padding: 0; font: inherit; cursor: pointer;
  color: inherit; text-decoration: underline; text-underline-offset: 2px;
}

/* ── glass skin ────────────────────────────────────────────────────────── */
body.glass .rep-sub, body.glass .rep-field > span, body.glass .rep-quick-label,
body.glass .rep-count, body.glass .rl-meta { color: rgba(244,243,240,.72); }
body.glass .rep-tab { color: rgba(244,243,240,.66); }
body.glass .rep-tab:hover, body.glass .rep-tab.on { color: #f4f3f0; }
body.glass .rep-tabs { border-bottom-color: rgba(255,255,255,.14); }
body.glass .chip { border-color: rgba(255,255,255,.20); color: rgba(244,243,240,.78); }
body.glass .chip:hover { border-color: rgba(255,255,255,.42); color: #f4f3f0; }
body.glass .chip.on { background: #f4f3f0; border-color: #f4f3f0; color: #1c1e21; }

/* ── print ─────────────────────────────────────────────────────────────
   Print / save as PDF is the second half of Export, so the page has to come
   off the screen cleanly: no chrome, no photograph, black on white, and the
   table allowed to break across pages at row boundaries. */
@media print {
  body, body.glass { background: #fff !important; }
  body.glass::before, .sidebar, .rep-tabs, .rep-quick, .rep-cols, .demo-ribbon,
  .composer-row, .home2-bar, #cursor, .toast, .histmenu, .rep-form { display: none !important; }
  .shell, body.glass .shell { display: block !important; background: #fff !important; }
  .content { overflow: visible !important; padding: 0 !important; }
  .reports, .panel, body.glass .panel {
    background: #fff !important; box-shadow: none !important;
    border: 0 !important; color: #000 !important;
    backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
  }
  .rep-head h2, .rep-sub, .rep-count, .table, .table th, .table td,
  body.glass .rep-count, body.glass .rep-sub { color: #000 !important; }
  .table-wrap { overflow: visible !important; }
  .table { width: 100%; border-collapse: collapse; font-size: 10.5pt; }
  .table th, .table td { border-bottom: 1px solid #bbb; padding: 5px 7px; }
  .table thead { display: table-header-group; }   /* repeat headers on every page */
  .table tr { break-inside: avoid; }
  .hint { display: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════════
   MESSAGES
   ═══════════════════════════════════════════════════════════════════════ */

/* The count of things nobody has answered. Sits on the nav item rather than
   floating, so a collapsed rail still shows it. */
.ni-badge {
  margin-left: auto; min-width: 20px; padding: 1px 6px;
  border-radius: 999px; font-size: 11.5px; font-weight: 700; line-height: 17px;
  text-align: center; background: #c8663a; color: #fff;
}
.sidebar.mini .ni-badge {
  position: absolute; top: 2px; right: 2px; margin: 0;
  min-width: 16px; padding: 0 4px; font-size: 10px; line-height: 15px;
}
.sidebar.mini .nav-item { position: relative; }

/* inline-flex so that a screen with its own top-row buttons — the client page
   has "Message about this client" — keeps them on the SAME line rather than
   pushing them down onto one of their own. */
.backbar { display: inline-flex; vertical-align: middle; margin-bottom: var(--space-3); }
.backbar + .clientbar-top { display: inline-flex; vertical-align: middle; margin-left: var(--space-2); }
/* Inside the message list column it is a header rather than a page element,
   so it takes that column's padding instead of the content wrapper's. */
.chat-list .backbar { display: flex; padding: var(--space-3) var(--space-3) 0; margin-bottom: 0; }

.clientbar-top {
  display: flex; align-items: center; gap: var(--space-2);
  margin-bottom: var(--space-3); flex-wrap: wrap;
}

/* ── glass ─────────────────────────────────────────────────────────────── */

/* ── conversation view ────────────────────────────────────────────────────
   The shape every phone already taught everyone: people down the side, the
   exchange in the middle, newest at the bottom, yours on the right. Rendered
   `bare` so it owns the full height instead of scrolling inside a page. */
.chat { display: flex; height: 100%; min-height: 0; background: var(--color-bg); }

.chat-list {
  width: 288px; flex: 0 0 288px; overflow-y: auto;
  background: var(--color-surface);
  border-right: 1px solid var(--color-divider);
  padding: var(--space-4) var(--space-3);
  display: flex; flex-direction: column; gap: 2px;
}
.chat-list-head {
  font-size: 12px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--color-text-dim); padding: 0 var(--space-2) var(--space-2);
}
.chat-filter { padding: 0 var(--space-2) var(--space-3); }
.chat-filter .select { width: 100%; font-size: 13px; padding: 8px 30px 8px 10px; }
.chat-filter-note { font-size: 11.5px; line-height: 1.45; margin-top: 6px; color: var(--color-text-dim); }
.chat-none { padding: var(--space-4) var(--space-2); font-size: 13px; color: var(--color-text-dim); }

.chat-peer {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3); border: 0; border-radius: 14px;
  background: none; font: inherit; text-align: left; cursor: pointer; width: 100%;
  color: inherit; transition: background .14s;
}
.chat-peer:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.chat-peer.on { background: color-mix(in srgb, var(--color-text) 10%, transparent); }
.cp-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cp-top { display: flex; align-items: baseline; gap: var(--space-2); }
.cp-top b { flex: 1 1 auto; font-size: 14.5px; }
.cp-top time { font-size: 11.5px; color: var(--color-text-dim); }
/* One line, always. A preview that wraps turns the list into a wall. */
.cp-last {
  font-size: 13px; color: var(--color-text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-peer .ni-badge { margin-left: 0; }

.chat-thread { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.chat-head {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--color-divider); flex: 0 0 auto;
}
.ch-text { display: flex; flex-direction: column; }
.ch-text b { font-size: 16px; }
.chat-sub { font-size: 12.5px; color: var(--color-text-dim); }

.chat-stream {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: var(--space-5) var(--space-6);
  display: flex; flex-direction: column; gap: var(--space-2);
}
.chat-empty {
  margin: auto; max-width: 44ch; text-align: center;
  font-size: 14px; color: var(--color-text-dim); line-height: 1.6;
}

/* A date, once, where the day turns over. */
.chat-day { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-3) 0; }
.chat-day::before, .chat-day::after {
  content: ''; flex: 1 1 auto; height: 1px; background: var(--color-divider);
}
.chat-day span {
  font-size: 11.5px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--color-text-dim);
}

/* Not something anybody said, so not a bubble. */
.chat-sys {
  align-self: center; font-size: 12px; font-style: italic;
  color: var(--color-text-dim); padding: 2px 0;
}

.bubble-row { display: flex; }
.bubble-row.me { justify-content: flex-end; }
.bubble {
  max-width: min(64%, 60ch);
  display: flex; flex-direction: column; gap: 5px;
  padding: 10px 14px; border-radius: 18px;
  font-size: 14.5px; line-height: 1.5;
}
/* The corner nearest the speaker is squared off — the whole tail cue, without
   drawing an actual tail. */
.bubble-row.them .bubble {
  background: var(--color-neutral-200); color: var(--color-text);
  border-bottom-left-radius: 5px;
}
.bubble-row.me .bubble {
  background: #c8663a; color: #fff; border-bottom-right-radius: 5px;
}
.bubble time { font-size: 11px; opacity: .72; align-self: flex-end; }
.bubble-body { white-space: pre-wrap; word-break: break-word; }

/* Still waiting on you, however chat-like the frame gets. */
.bubble-open { box-shadow: 0 0 0 2px #c8663a; }
.bubble-ack { align-self: flex-start; margin-top: 2px; }

.bubble-client {
  align-self: flex-start; border: 0; background: rgba(0,0,0,.10);
  color: inherit; font: inherit; font-size: 12px; font-weight: 600;
  padding: 2px 8px; border-radius: 999px; cursor: pointer;
}
.bubble-row.me .bubble-client { background: rgba(255,255,255,.22); }

.chat-compose {
  flex: 0 0 auto; display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-divider);
}
.chat-client { width: auto; min-width: 130px; max-width: 190px; font-size: 13px; padding: 9px 30px 9px 12px; }
.chat-input { flex: 1 1 auto; border-radius: 999px; }
.chat-send {
  flex: 0 0 auto; width: 42px; height: 42px; border-radius: 999px; border: 0;
  background: #c8663a; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: filter .14s;
}
.chat-send:hover { filter: brightness(1.12); }
.chat-send svg {
  width: 19px; height: 19px; fill: none; stroke: #fff;
  stroke-width: 1.8; stroke-linejoin: round;
}

@media (max-width: 880px) {
  .chat { flex-direction: column; }
  .chat-list { width: auto; flex: 0 0 auto; max-height: 34vh; border-right: 0;
               border-bottom: 1px solid var(--color-divider); }
}

/* ── glass ─────────────────────────────────────────────────────────────── */
/* Two different jobs, so two different weights of glass.

   The list is scanning: short lines, names, times, read at a glance and often
   out of the corner of an eye. It sits heavier, so it holds still.

   The thread is the photograph's share of the screen. Bubbles carry their own
   backgrounds, so the only text sitting directly on the glass here is the day
   dividers and the acknowledgement lines — and both are short. It can afford
   to be thin. Blur on each, because a photograph is detail and detail under
   small type is what makes it unreadable, not brightness. */
body.glass .chat { background: transparent; }
body.glass .chat-list {
  background: rgba(44, 42, 39, .80);
  -webkit-backdrop-filter: blur(26px) saturate(1.1);
          backdrop-filter: blur(26px) saturate(1.1);
}
body.glass .chat-thread {
  background: rgba(52, 50, 46, .44);
  -webkit-backdrop-filter: blur(20px) saturate(1.1);
          backdrop-filter: blur(20px) saturate(1.1);
}
/* The two bars top and bottom hold controls, so they take the list's weight
   rather than the thread's. */
body.glass .chat-head, body.glass .chat-compose { background: rgba(44, 42, 39, .55); }

body.glass .chat-list, body.glass .chat-head, body.glass .chat-compose {
  border-color: rgba(255,255,255,.12);
}
body.glass .chat-list-head, body.glass .cp-last, body.glass .cp-top time,
body.glass .chat-sub, body.glass .chat-sys, body.glass .chat-day span,
body.glass .chat-empty, body.glass .chat-filter-note,
body.glass .chat-none { color: rgba(244,243,240,.66); }
/* On thinner glass these two are the only text without a bubble under it. */
body.glass .chat-sys, body.glass .chat-day span {
  text-shadow: 0 1px 3px rgba(14,12,10,.55);
}
body.glass .chat-peer:hover { background: rgba(255,255,255,.08); }
body.glass .chat-peer.on { background: rgba(255,255,255,.14); }
body.glass .chat-day::before, body.glass .chat-day::after { background: rgba(255,255,255,.14); }
body.glass .bubble-row.them .bubble {
  background: rgba(255,255,255,.13); color: #f4f3f0;
}
body.glass .bubble-row.them .bubble-client { background: rgba(255,255,255,.16); }

/* ── account manager ──────────────────────────────────────────────────── */
.acct-grid {
  display: grid; gap: var(--space-4); margin-top: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
}
.acct { display: flex; flex-direction: column; gap: var(--space-4); }
.acct-off { opacity: .6; }

.acct-head { display: flex; align-items: center; gap: var(--space-3); }
.acct-who { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.acct-who b { font-size: 16px; }
.acct-sub { font-size: 12.5px; color: var(--color-text-dim); }
.acct-you {
  font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  padding: 1px 6px; border-radius: 999px; background: rgba(255,255,255,.16); vertical-align: 2px;
}

/* Four numbers, equal weight, no ranking implied between people. */
.acct-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-2); }
.acct-stat {
  display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  padding: var(--space-3) var(--space-2); border-radius: 12px;
  background: color-mix(in srgb, var(--color-text) 5%, transparent);
}
.acct-stat b { font-size: 21px; line-height: 1; font-variant-numeric: tabular-nums; }
.acct-stat span { font-size: 10.5px; line-height: 1.3; color: var(--color-text-dim); }
/* Settled is the only one that means an outcome rather than an activity. */
.acct-stat-on b { color: #7f9a5f; }

.acct-role { display: flex; flex-direction: column; gap: 4px; }
.acct-role > span {
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--color-text-dim);
}
.acct-role .select:disabled { opacity: .55; cursor: not-allowed; }
.acct-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

body.glass .acct-sub, body.glass .acct-stat span,
body.glass .acct-role > span { color: rgba(244,243,240,.66); }
body.glass .acct-stat { background: rgba(255,255,255,.07); }

/* A heading sits straight on the photograph and is large enough to survive it
   with an edge. Prose is not, and gets a panel instead — see .note. */
body.glass .content > h2, body.glass .content > h3 {
  text-shadow: 0 1px 4px rgba(14, 12, 10, .8), 0 0 18px rgba(14, 12, 10, .5);
}

/* Explanatory copy that would otherwise sit on the photograph. */
.note { max-width: 74ch; margin-bottom: var(--space-5); }
.note p { margin: 0 0 var(--space-3); font-size: 13.5px; line-height: 1.6; color: var(--color-text-dim); }
.note p:last-of-type { margin-bottom: 0; }
body.glass .note p { color: rgba(244, 243, 240, .82); }
body.glass .note p b { color: #f4f3f0; }

/* ── one person's record ──────────────────────────────────────────────── */
/* The card header is the way in, so it has to look like one without becoming
   a button sitting inside a card full of buttons. */
.acct-head {
  display: flex; align-items: center; gap: var(--space-3);
  width: 100%; padding: var(--space-2); margin: calc(var(--space-2) * -1);
  border: 0; background: none; font: inherit; color: inherit;
  text-align: left; cursor: pointer; border-radius: 12px;
  transition: background .14s;
}
.acct-head:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.acct-go { opacity: 0; font-size: 18px; transition: opacity .14s; }
.acct-head:hover .acct-go { opacity: .7; }
body.glass .acct-head:hover { background: rgba(255,255,255,.09); }

.person-head {
  display: flex; align-items: center; gap: var(--space-4);
  flex-wrap: wrap; margin-bottom: var(--space-4);
}
.person-stats {
  margin-left: auto; display: grid; gap: var(--space-2);
  grid-template-columns: repeat(4, minmax(84px, 1fr));
}
@media (max-width: 720px) { .person-stats { margin-left: 0; width: 100%; } }

/* ── the case, above one person's work on it ──────────────────────────── */
.case-head { margin-bottom: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
.case-top { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.case-top h3 { margin: 0; font-size: 19px; }
.case-num { font-size: 13px; color: var(--color-text-dim); font-variant-numeric: tabular-nums; }
/* Pushed right so it reads as a footnote to the name rather than part of it. */
.case-share { margin-left: auto; font-size: 12.5px; color: var(--color-text-dim); }

body.glass .case-num, body.glass .case-share { color: rgba(244,243,240,.66); }

/* ── the row menu ─────────────────────────────────────────────────────── */
/* Pinned to the right edge of the scroller. The table is wider than the
   window at ordinary sizes, so an action column that scrolled with it would be
   the one column you can never reach. It needs an opaque background of its own
   because sticky cells sit over the ones passing underneath. */
.t-menu {
  width: 48px; text-align: right;
  position: sticky; right: 0;
  background: var(--color-surface);
  box-shadow: -8px 0 12px -8px rgba(0,0,0,.28);
}
body.glass .t-menu { background: var(--glass-strong); }
/* The hover tint has to be painted on the sticky cell too, or the row appears
   to stop short of it. */
.table tbody tr:hover .t-menu {
  background: color-mix(in srgb, var(--color-text) 4%, var(--color-surface));
}
body.glass .table tbody tr:hover .t-menu {
  background: color-mix(in srgb, #ffffff 5%, var(--glass-strong));
}
.rowmenu {
  display: inline-flex; flex-direction: column; justify-content: center; gap: 3px;
  width: 28px; height: 28px; padding: 0 6px; border: 0; border-radius: 8px;
  background: none; cursor: pointer; opacity: .8; transition: opacity .14s, background .14s;
}
tr:hover .rowmenu { opacity: 1; }
.rowmenu:hover { background: color-mix(in srgb, var(--color-text) 10%, transparent); opacity: 1; }
/* Stated, not inherited. The cell it sits in carries the dimmed body colour,
   which made the bars nearly invisible against the panel. */
.rowmenu span { display: block; height: 2px; border-radius: 2px; background: var(--color-text); }
body.glass .rowmenu span { background: #f4f3f0; }
body.glass .rowmenu:hover { background: rgba(255,255,255,.14); }

/* Opened on click and fanned left, because the button sits against the right
   edge of the table and a full circle would be half off screen. */
.rowradial { position: fixed; z-index: 300; width: 0; height: 0; }
.rr-item {
  position: absolute; left: 0; top: 0; border: 0; background: none; padding: 0;
  cursor: pointer; font: inherit; color: inherit;
  display: flex; align-items: center; gap: 8px;
  transform: translate(-50%, -50%) scale(.6); opacity: 0;
  transition: transform .22s cubic-bezier(.2,.9,.3,1.3), opacity .18s;
  transition-delay: var(--d, 0ms);
  white-space: nowrap;
}
.rowradial.on .rr-item {
  transform: translate(calc(-50% + var(--x)), calc(-50% + var(--y))) scale(1);
  opacity: 1;
}
.rr-btn {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: #3a3834; box-shadow: 0 6px 18px rgba(14,12,10,.5);
}
.rr-btn svg {
  width: 17px; height: 17px; fill: none; stroke: #f4f3f0;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.rr-item:hover .rr-btn { background: #c8663a; }
.rr-label {
  order: -1; font-size: 12.5px; color: #f4f3f0;
  background: rgba(24,22,20,.9); padding: 4px 9px; border-radius: 999px;
}

/* ── a cited entry ────────────────────────────────────────────────────── */
.quote {
  border-left: 3px solid currentColor; padding: 6px 0 6px 10px;
  display: flex; flex-direction: column; gap: 3px;
  opacity: .92;
}
.quote-meta {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: 11.5px; opacity: .8;
}
.quote-meta b { font-size: 12px; }
.quote-note { margin: 0; font-size: 13px; line-height: 1.45; }
.quote-drop {
  margin-left: auto; border: 0; background: none; color: inherit;
  font-size: 16px; line-height: 1; cursor: pointer; opacity: .7; padding: 0 2px;
}
.quote-drop:hover { opacity: 1; }

/* Waiting in the composer rather than sent — it sits on the bar, so it needs
   its own edge. */
.chat-attached { padding: var(--space-3) var(--space-4) 0; }
.quote-draft {
  background: color-mix(in srgb, var(--color-text) 6%, transparent);
  border-radius: 0 10px 10px 0; padding: 8px 10px;
}
body.glass .quote-draft { background: rgba(255,255,255,.09); }
body.glass .chat-attached { border-top: 1px solid rgba(255,255,255,.12); margin-top: 0; }

/* ── server connection ────────────────────────────────────────────────── */
.conn { font-weight: 600; font-size: 15px; }
/* A dot rather than colour alone, so the state survives a colourblind reader
   and a black-and-white print. */
.conn::before {
  content: ''; display: inline-block; width: 9px; height: 9px;
  border-radius: 999px; margin-right: 8px; vertical-align: 1px;
  background: currentColor;
}
.conn-ok   { color: #7f9a5f; }
.conn-bad  { color: #c8663a; }
.conn-wait { color: var(--color-text-dim); }
.conn-ms { margin-left: 10px; font-size: 12.5px; color: var(--color-text-dim); font-variant-numeric: tabular-nums; }

.conn-table {
  margin-top: var(--space-3); display: flex; flex-direction: column;
  border-top: 1px solid var(--color-divider);
}
.conn-row {
  display: flex; gap: var(--space-4); padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-divider); font-size: 13.5px;
}
.conn-k { flex: 0 0 172px; color: var(--color-text-dim); }
.conn-v { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; word-break: break-word; }
.conn-v code {
  font-family: ui-monospace, Menlo, monospace; font-size: 13px;
  background: color-mix(in srgb, var(--color-text) 7%, transparent);
  padding: 2px 6px; border-radius: 6px; user-select: all;
}
.conn-note { font-size: 12px; color: var(--color-text-dim); line-height: 1.45; }

body.glass .conn-table, body.glass .conn-row { border-color: rgba(255,255,255,.12); }
body.glass .conn-k, body.glass .conn-note, body.glass .conn-ms { color: rgba(244,243,240,.66); }
body.glass .conn-v code { background: rgba(255,255,255,.10); }
body.glass .conn-wait { color: rgba(244,243,240,.66); }

/* ── the command bar ──────────────────────────────────────────────────────
   There is no way to put an element inside an <input>, so the chip lives on a
   layer over the field: a ghost span mirrors what has been typed, in the
   field's own font and metrics, and the chip follows it. Nothing here takes
   the pointer — the field underneath must stay clickable. */
/* Sits exactly over the field and draws its text. The transparent border is
   not decoration — the input has a 1px one, and without a matching one here
   every glyph lands a pixel left of the caret. */
.slotline {
  position: absolute; inset: 0; pointer-events: none;
  box-sizing: border-box; border: 1px solid transparent;
  /* Block, not flex, so the text wraps the way the field's does. The chip and
     the ghost were flex items when there was only ever one line to lay out. */
  padding: 18px 72px 18px 26px; font-size: 19px;
  white-space: pre-wrap; overflow-wrap: anywhere; overflow: hidden;
  color: var(--bar-ink);
  /* overflow:hidden clips at the BORDER box, so a long line would paint into
     the right-hand padding and straight under the send button. The input never
     does that — it scrolls to keep the caret inside its content box — so the
     mirror has to be told where the content ends. */
  clip-path: inset(0 62px 0 0);
}
.slot-ghost, .slot-hint { display: inline; }

/* What Donellus understood, coloured where it was typed. The stage carries the
   emphasis because it is the operative word — it decides where the case goes.
   The name is marked more quietly: useful confirmation, not the point. */
/* COLOUR ONLY — never weight, never spacing, never horizontal padding.
   This layer is drawn over the real text and has to track it character for
   character. A bolder face is a wider face: at 650 against the field's 400,
   "Sending message to Daytona:" alone pushed everything after it 6px right of
   the characters it was supposed to be sitting on, and the caret with it. */
.hot-status { color: #c8663a; }
.hot-client { color: #c8663a; opacity: .68; }
/* What the client is reported to have said, after "he says" or "they told me".
   A different colour because it is a different kind of claim: the orange parts
   are what Donellus worked out and is about to act on, and this is somebody
   else's account, which Donellus is only carrying. */
.hot-said { color: #4f7fbf; }
body.glass .hot-said { color: #8ab4f0; }
/* The verb that decides how this gets filed — "called", "emailed", "texted".
   Green, the same green the category label wears on the finished entry, so
   the word and the label it produces are visibly the same fact. */
/* Who a message is going to. Red because it is the one thing in this bar
   addressed at a colleague rather than a case, and mistaking the two would
   mean saying something to the wrong audience. */
/* The address is no longer drawn over the text — it is a chip at the head of
   the bar, so it can be one. Nothing is left to style here. */

/* The same green for every verb, not only the ones that file an entry.
   "Called" and "filter" are the same kind of word — what you are doing — and
   giving the filing verbs a colour the others do not have would say they were
   a different kind of thing, which was never true. */
.hot-method, .hot-verb { color: #2f6b46; }
/* A colleague. Purple because the two names in this bar are the two things
   it is most costly to confuse: a client is the file you are working, a
   colleague is a person you are working WITH, and "Sheree's clients" means
   something very different from a client called Sheree. Orange is spoken for,
   so the distinction is carried by a colour that borrows from neither it nor
   the blue of what somebody was reported to have said. */
.hot-user { color: #6f42a8; }
/* A name the bar recognised but cannot resolve to one person. The client's
   own colour, so it is plainly a name, under a dotted rule that says it is
   not settled. Decoration only — never weight or spacing, which move the
   caret under the text this is drawn over. */
.hot-vague { color: #c8663a; opacity: .68;
             text-decoration: underline dotted currentColor;
             text-underline-offset: 3px; }
body.glass .hot-method, body.glass .hot-verb { color: #8fd0aa; }
/* Lifted well off the wall behind it. #6f42a8 on a lit brown room is a
   smudge — dark on dark, at 14px, in a field somebody is reading while they
   type into it. */
body.glass .hot-user { color: #cbaaf7; }
/* One definition for both chips. The address chip and the slot chip are the
   same object in two colours — that is the whole point of taking the address
   out of the input, where it could only ever borrow the field's face. */
.slot-chip, .barchip {
  display: inline-block; vertical-align: baseline;
  /* text-indent INHERITS, and the mirror sets one to step the first line past
     the address chip. An inline-block applies it to its own first line too, so
     the chip grew by the width of the indent and sat "Who?" out at the far
     end of a box of empty orange. */
  text-indent: 0;
  padding: 3px 10px; border-radius: 8px;
  font-size: 14px; font-weight: 700; letter-spacing: .02em;
  color: #fff; background: #c8663a;
  white-space: nowrap;
}
.slot-chip {
  margin-left: 14px;
  box-shadow: 0 0 0 1px rgba(200,102,58,.5), 0 0 14px rgba(200,102,58,.75);
  animation: slotGlow 1.6s ease-in-out infinite;
}

/* The chip rail sits inside the field's left padding. */
.barchips {
  /* Pinned to the FIRST line rather than the middle of the box: the box grows
     downward as the message wraps, and a chip drifting to the centre of it
     would end up beside the second line of a message it introduces. */
  position: absolute; left: 26px; top: 19px;
  display: flex; align-items: center; gap: 6px; z-index: 2; pointer-events: none;
}
/* `display` beats the hidden attribute, so setting .hidden on a flex element
   does nothing at all. The chip stayed painted over the text it had just
   handed back. */
.barchips[hidden] { display: none; }
.barchip-to { background: #b03636; }
@keyframes slotGlow {
  0%, 100% { box-shadow: 0 0 0 1px rgba(200,102,58,.5), 0 0 10px rgba(200,102,58,.55); }
  50%      { box-shadow: 0 0 0 1px rgba(200,102,58,.7), 0 0 20px rgba(200,102,58,.95); }
}
@media (prefers-reduced-motion: reduce) { .slot-chip { animation: none; } }
/* Nothing is being waited for, so it does not pulse. It is an answer. */
.slot-chip.done {
  animation: none; background: rgba(200,102,58,.16); color: #c8663a;
  box-shadow: inset 0 0 0 1px rgba(200,102,58,.45);
  font-weight: 600;
}

/* ── send ──────────────────────────────────────────────────────────────── */
.bigsend {
  position: absolute; right: 10px; top: 11px;
  width: 46px; height: 46px; border-radius: 999px;
  display: grid; place-items: center; cursor: pointer;
  background: #fff; border: 1px solid rgba(28,30,33,.10);
  box-shadow: 0 1px 4px rgba(28,30,33,.16);
  /* The turn is the long one; press and hover stay quick. */
  transition: transform .34s cubic-bezier(.32, .72, 0, 1),
              box-shadow .12s ease, background .12s ease;
}
.bigsend svg {
  width: 21px; height: 21px; fill: none; stroke: #1c1e21;
  stroke-width: 2.1; stroke-linecap: round; stroke-linejoin: round;
}

/* ── the arrow turns to face the letter ─────────────────────────────────

   While a demand is being built the bar is no longer running a search: it
   is answering the question in the panel above it, and Enter sends the
   answer UP into the letter rather than forward into a result. The arrow
   points where the answer is going.

   -90deg, not 90: the arrow rotates anticlockwise so it sweeps through the
   side the letter is on. Turning the other way is the same end state and
   reads as the arrow going the long way round.

   Transitioned rather than swapped, because the turn is what says the bar
   changed jobs — an arrow that is simply up on the next frame looks like a
   different arrow. It turns back on its own when the interview ends, since
   the class goes with it. */
/* The rotation itself is set on the element by public/interview.js — see
   turnArrow() there for why. What stays here is the TRANSITION, which is
   what makes it a turn rather than a swap, and the pressed state.

   :active has to restate the rotation, because transform is one property:
   a press that only said `scale(.94)` would drop the turn and snap the
   arrow back to horizontal for as long as the mouse was down. */
body.interviewing .bigsend:active { transform: rotate(-90deg) scale(.94); }

/* Somebody who has asked for less movement gets the end state without the
   sweep, the same as every other transition in this stylesheet. */
@media (prefers-reduced-motion: reduce) {
  .bigsend { transition: none; }
}
.bigsend:hover { background: #fff; box-shadow: 0 3px 12px rgba(28,30,33,.24); }
.bigsend:active { transform: scale(.94); }
.bigsend:focus-visible { outline: 2px solid #c8663a; outline-offset: 2px; }

/* The other corner. Same button as the arrow — a search bar that can be
   spoken to needs somewhere that says what it will understand, and the place
   to ask is beside the thing you would be asking about. Bottom left, opposite
   the workload dial, so the two corners hold the two standing questions:
   what can I say, and how much is left. */
.bighelp {
  /* Absolute inside .home2, not fixed to the window. .home2 is the flex
     sibling that starts where the sidebar ends, so this is always clear of
     the rail — and stays clear when the rail expands from 66px to 232px,
     with no width to hard-code and nothing to keep in step. Fixed at 44px
     from the window's edge, it sat underneath the rail's own buttons. */
  position: absolute; left: 30px; bottom: 26px; z-index: 24;
  width: 46px; height: 46px; border-radius: 999px; padding: 0;
  display: grid; place-items: center; cursor: pointer;
  background: #fff; border: 1px solid rgba(28,30,33,.10);
  box-shadow: 0 6px 16px rgba(18,14,10,.42);
  transition: transform .16s cubic-bezier(.2,.9,.28,1.25), box-shadow .12s ease;
}
.bighelp svg {
  width: 21px; height: 21px; fill: none; stroke: #1c1e21;
  stroke-width: 2.1; stroke-linecap: round; stroke-linejoin: round;
}
.bighelp:hover { transform: scale(1.08); box-shadow: 0 8px 22px rgba(18,14,10,.5); }
.bighelp:active { transform: scale(.95); }
.bighelp:focus-visible { outline: 2px solid #c8663a; outline-offset: 3px; }

/* ── what you can type ──────────────────────────────────────────────────
   Categories down the left, the chosen one beside them. Two columns of one
   grid inside .content, so the list keeps the same margin from the app's rail
   as every other panel on this screen — the background shows between them,
   and the page reads as one thing with a spine rather than as a second
   sidebar bolted to the first. */
.helpsplit {
  display: grid; grid-template-columns: 232px minmax(0, 1fr);
  gap: var(--space-4); align-items: start;
}
/* .setblock caps itself at 760px and adds a bottom margin, neither of which
   suits a column in a two-column grid. It is used for the surface and the
   border so these match every other panel in the app. */
.helpnav, .helppanes { max-width: none; margin-bottom: 0; }
.helpnav {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--space-3); position: sticky; top: var(--space-4);
}
.helpcat {
  display: flex; align-items: center; gap: var(--space-3);
  width: 100%; text-align: left; font: inherit; font-size: 14.5px;
  padding: 10px 12px; border-radius: 10px; cursor: pointer;
  border: 1px solid transparent; background: transparent;
  color: var(--color-neutral-700);
  transition: background .14s ease, color .14s ease, border-color .14s ease;
}
.helpcat-t { flex: 1; min-width: 0; }
.helpcat-n {
  font-size: 12px; font-variant-numeric: tabular-nums; opacity: .5;
}
.helpcat:hover { color: var(--color-neutral-900); background: var(--color-neutral-100); }
.helpcat.on {
  color: var(--color-neutral-900); font-weight: 650;
  background: var(--color-neutral-200); border-color: var(--hair);
}
.helpcat:focus-visible { outline: 2px solid #c8663a; outline-offset: 2px; }

/* One box, one panel at a time. A min-height so that moving down the list
   does not make the page grow and shrink under the pointer. */
.helppanes { padding: var(--space-4) var(--space-6); min-height: 340px; }
.helppane { display: none; }
.helppane.on { display: block; }
.helppane h3 { margin: 0 0 4px; font-size: 17px; }

/* The row reaches a little past its text on both sides — negative margin out,
   padding back in — so the highlight has room to breathe without a spread
   shadow.

   It used to get that room from `box-shadow: 0 0 0 12px`, and a spread grows
   in EVERY direction: the highlight bled twelve pixels up and down into the
   rows either side, so hovering one command lit part of its neighbours and
   there was no telling which one a click would take. Size the box, do not
   inflate the paint. */
.helprow {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-3) var(--space-4); align-items: baseline;
  margin-inline: calc(var(--space-3) * -1);
  padding: 10px var(--space-3);
  /* Explicit, not auto: a <button> shrinks to fit whatever is inside it, so
     `auto` collapsed the two columns onto the text and left the descriptions
     hanging off the end of each command. This is the content width plus the
     two negative margins, which is exactly the panel's padding box. */
  width: calc(100% + var(--space-3) * 2);
  text-align: left; font: inherit; color: inherit;
  background: transparent; border: none; border-radius: 8px;
}
/* The separator is a pseudo rather than a border, so it can stay inset while
   the highlight goes full width, and so a rounded corner is not drawn on top
   of a straight line. Between rows only — never above the first. */
.helprow + .helprow::before {
  content: ''; position: absolute; top: 0;
  left: var(--space-3); right: var(--space-3);
  border-top: 1px solid var(--hair);
}
.helprow.go { cursor: pointer; }
/* A row that cannot be prefilled is an example rather than a button, and has
   to look like one — otherwise the only difference between "this does
   nothing" and "this is broken" is that you tried it. */
.helprow:not(.go) { opacity: .68; }
.helprow.go:hover, .helprow.go:focus-visible { background: var(--color-neutral-100); }
.helprow.go:focus-visible { outline: 2px solid #c8663a; outline-offset: -2px; }

@media (max-width: 860px) {
  .helpsplit { grid-template-columns: 1fr; }
  .helpnav { position: static; flex-direction: row; flex-wrap: wrap; }
  .helpcat { width: auto; }
}
.helpsay {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; color: var(--color-text);
}
.helpsay em { font-style: normal; color: #c8663a; }
/* The guide shows a phrase in the colours the bar will show it in, so these
   are the .hot-* values and have to stay the same values. A legend that
   drifts from the thing it explains is worse than no legend at all.
   <b> here is a word typed as written, not a word said loudly. */
.helpsay b { font-weight: inherit; }
.helpsay .k-verb { color: #2f6b46; }
.helpsay .k-user { color: #6f42a8; }
.helpsay .k-said { color: #4f7fbf; }
/* Screen names and switches. The same orange as .hot-status, because that is
   literally what the bar paints them, and this value has no glass variant
   there either — it carries on both grounds. */
.helpsay .k-place { color: #c8663a; }
/* And the lifted set, because this page renders over the room as often as
   not, and the legend has to match the bar on whichever ground it lands on.
   A dark green on a dark panel is not a quieter green, it is an absent one. */
body.glass .helpsay .k-verb { color: #8fd0aa; }
body.glass .helpsay .k-user { color: #cbaaf7; }
body.glass .helpsay .k-said { color: #8ab4f0; }
.helpdoes { font-size: 13.5px; color: var(--color-neutral-600); }
.helpnote { font-size: 13.5px; color: var(--color-neutral-600); margin: 2px 0 10px; }
@media (max-width: 760px) { .helprow { grid-template-columns: 1fr; } }

/* The line that says what return will do. */
.ac-go {
  padding: 7px 14px; margin-top: 2px;
  border-top: 1px solid var(--color-divider);
  font-size: 12.5px; font-weight: 600; color: #c8663a;
}
body.glass .ac-go { border-top-color: rgba(255,255,255,.14); }

/* ── a staged status change ───────────────────────────────────────────── */
.stagebar {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3); margin-bottom: var(--space-4);
  border-radius: 12px; border-left: 3px solid #c8663a;
  background: color-mix(in srgb, #c8663a 10%, transparent);
}
.stage-what { display: flex; align-items: center; gap: 6px; font-size: 14px; }
.stage-arrow { opacity: .7; }
.stage-why { flex: 1 1 220px; font-size: 12.5px; color: var(--color-text-dim); }
.panel.staged { box-shadow: 0 0 0 1px rgba(200,102,58,.45), var(--shadow-sm); }
.select-staged { box-shadow: 0 0 0 2px rgba(200,102,58,.55); }

body.glass .stagebar { background: rgba(200,102,58,.18); }
body.glass .stage-why { color: rgba(244,243,240,.72); }

/* ── The rest of the sentence, written into the bar ────────────────────────
   Sits in the same mirrored line as the slot chip, immediately after the text
   that has been typed, in the field's own font — so it reads as one sentence
   half-written rather than as a label parked nearby. Quiet enough to be
   plainly unwritten, bright enough to be worth reading. */
.slot-hint {
  flex: 0 0 auto;
  color: rgba(244,243,240,.42);
}
body:not(.glass) .slot-hint { color: rgba(31,29,26,.38); }

/* ── Pointing at the stage control ─────────────────────────────────────────
   Three seconds after arriving from "update Abbott" with no stage named. It
   says "here is the thing you came to change" and then gets out of the way;
   anything that outlasts the glance becomes part of how the page looks. */
.pointed { animation: pointOut 3s ease-out 1 both; border-radius: inherit; }
.typepick.pointed { border-radius: 12px; }
@keyframes pointOut {
  0%, 60%  { box-shadow: 0 0 0 2px rgba(200,102,58,.85), 0 0 18px rgba(200,102,58,.7);
             border-color: #c8663a; }
  100%     { box-shadow: 0 0 0 0 rgba(200,102,58,0), 0 0 0 rgba(200,102,58,0); }
}
@media (prefers-reduced-motion: reduce) {
  .pointed { animation: none; box-shadow: 0 0 0 2px rgba(200,102,58,.85); }
}

/* ── the two labels on an entry ────────────────────────────────────────────
   What kind of work it was, and how the person was reached. Different colours
   because they answer different questions and are disputed differently: a
   client argues about whether they were called, not about whether the call was
   filed under follow-up. */
.lbl {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; border-radius: 999px;
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.lbl-kind { color: #2f6b46; background: rgba(47,107,70,.13); box-shadow: inset 0 0 0 1px rgba(47,107,70,.28); }
.lbl-how  { color: #2f5b8f; background: rgba(47,91,143,.13); box-shadow: inset 0 0 0 1px rgba(47,91,143,.28); }
body.glass .lbl-kind { color: #96d3ae; background: rgba(120,200,155,.16); box-shadow: inset 0 0 0 1px rgba(120,200,155,.34); }
body.glass .lbl-how  { color: #9dc2f0; background: rgba(120,165,230,.16); box-shadow: inset 0 0 0 1px rgba(120,165,230,.34); }

/* The composer's second chip row: how, rather than what. */
.methodpick { margin-top: calc(var(--space-2) * -1); }
.pick-lead {
  align-self: center; font-size: 12px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--color-neutral-600); margin-right: 2px;
}
.methodbtn.on { color: #2f5b8f; border-color: rgba(47,91,143,.55); background: rgba(47,91,143,.12); }
body.glass .methodbtn.on { color: #9dc2f0; border-color: rgba(120,165,230,.6); background: rgba(120,165,230,.18); }

/* ── the preview above the search bar ──────────────────────────────────────
   What the line you are typing is about to do, shown before you commit to it.
   Above the field because it is a picture of the destination and the eye is
   already up there; the list of things to pick from stays below, under your
   hand. The transparent bottom border keeps the gap inside the hover region,
   the same trick the list below uses at its top. */
.peek {
  position: absolute; left: 0; right: 0; bottom: 100%; z-index: 30;
  border-bottom: 10px solid transparent;
}
/* The same surface the client's own page is made of — var(--glass) over the
   photograph, the treatment every panel in the app already wears. A preview
   that looked like a different product than the page it previews is a preview
   you have to translate. */
.peek-card {
  background: #fff; background-clip: padding-box; cursor: pointer;
  border: 1px solid #ececef; border-radius: 18px; overflow: hidden;
  box-shadow: 0 10px 30px rgba(28,30,33,.16);
  transition: transform .16s cubic-bezier(.2,.8,.25,1), box-shadow .16s ease;
  /* Grows from its BOTTOM edge. The card is out of flow, so it cannot push the
     search bar however it scales — but growing from the middle would close the
     gap between the two, and a card creeping towards the field it sits above
     reads as the layout shifting even when nothing has moved. Anchored here,
     the gap is fixed and only the top and sides expand. */
  transform-origin: bottom center;
}
/* Hovered on the CONTAINER, not on the card.

   The card is rebuilt from scratch on every keystroke — that is how the
   preview keeps up — and a brand-new element does not inherit the hover state
   in the same tick. So with the pointer resting on it, each keystroke inserted
   a card at scale 1, hover was recomputed a moment later, and the .16s
   transition ran again: a pulse per character, which is what "the window
   vibrates when I type" was. Keying off the parent, which is never replaced,
   means the incoming card's first computed style is already the hovered one
   and there is nothing to animate. */
.peek:hover > .peek-card {
  transform: scale(1.022);
  box-shadow: 0 16px 42px rgba(28,30,33,.24);
}
.peek:active > .peek-card { transform: scale(1.005); }
@media (prefers-reduced-motion: reduce) {
  .peek-card, .peek:hover > .peek-card, .peek:active > .peek-card {
    transition: none; transform: none; }
}
body.glass .peek:hover > .peek-card {
  box-shadow: var(--lift), 0 16px 42px rgba(20,18,16,.36);
}
body.glass .peek-card {
  background: var(--glass);
  -webkit-backdrop-filter: blur(20px) saturate(1.1);
          backdrop-filter: blur(20px) saturate(1.1);
  border: 1px solid var(--hair);
  box-shadow: var(--lift), 0 10px 30px rgba(20,18,16,.28);
}
.peek-head {
  display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap;
  padding: 12px var(--space-4) 8px;
}
.peek-kind {
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: #fff; background: #c8663a; border-radius: 999px; padding: 2px 8px;
}
.peek-title { font-size: 18px; font-weight: 650; color: #1c1e21; }
.peek-sub { font-size: 13px; color: #6b7076; }
.peek-body { padding: 0 var(--space-4) 10px; display: grid; gap: 8px; }
.peek-stage { display: flex; align-items: center; gap: 8px; }
.peek-arrow { color: #c8663a; font-weight: 700; }
.peek-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.peek-note {
  border: 1px solid #e3e5e8; border-radius: 12px; background: #fafafa;
  padding: 9px 12px; font-size: 14.5px; color: #1c1e21; line-height: 1.45;
}
.peek-note.empty { color: #9aa0a6; }
.peek-foot {
  padding: 7px var(--space-4); font-size: 12px; color: #85898f;
  border-top: 1px solid #f0f0f2;
  transition: color .16s ease;
}
.peek:hover .peek-foot { color: #c8663a; }
/* Light type on the dark panel, and the entry box drawn exactly as the
   composer draws it on the client's page. */
body.glass .peek-title { color: #f4f3f0; }
body.glass .peek-sub { color: rgba(244,243,240,.62); }
body.glass .peek-note {
  background: rgba(20, 18, 16, .42);
  border-color: rgba(255,255,255,.22);
  color: #f4f3f0;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08);
}
body.glass .peek-note.empty { color: rgba(244,243,240,.52); }
body.glass .peek-foot { color: rgba(244,243,240,.55); border-top-color: rgba(255,255,255,.12); }
body.glass .peek-refer { color: rgba(244,243,240,.86); }
.peek-refer {
  border-left: 3px solid #c8663a; padding: 4px 0 4px 11px;
  font-size: 13.5px; line-height: 1.5; color: #4e535a;
}
body.glass .peek-refer { color: #3a3f45; }
.peek-swatches { display: flex; gap: 6px; }
.peek-swatch {
  width: 46px; height: 31px; border-radius: 7px; background-size: cover;
  background-position: center; box-shadow: inset 0 0 0 1px rgba(28,30,33,.12);
}

/* The send button, when what it would do is write into a record. */
.bigsend.asking {
  width: auto; padding: 0 16px; border-radius: 999px;
  background: #c8663a; border-color: #c8663a;
}
.bigsend.asking .sendword { font-size: 14px; font-weight: 650; color: #fff; white-space: nowrap; }
.bigsend.asking:hover { background: #b95a30; }
.bigsend.asking.to-person { background: #b03636; border-color: #b03636; }
.bigsend.asking.to-person:hover { background: #9c2e2e; }

/* One line of a conversation, in the preview. */
.peek-line { font-size: 13.5px; line-height: 1.5; color: #3a3f45; }
.peek-line.mine { color: #6b7076; }
.peek-dim { color: #9aa0a6; }
.peek-row { display: flex; align-items: baseline; gap: 8px; font-size: 13.5px; }
.peek-who { font-weight: 650; color: #1c1e21; white-space: nowrap; }
.peek-last { color: #6b7076; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.peek-badge {
  background: #c8663a; color: #fff; border-radius: 999px;
  padding: 0 7px; font-size: 11px; font-weight: 700;
}
body.glass .peek-line { color: rgba(244,243,240,.88); }
body.glass .peek-line.mine, body.glass .peek-last { color: rgba(244,243,240,.60); }
body.glass .peek-dim { color: rgba(244,243,240,.50); }
body.glass .peek-who { color: #f4f3f0; }

/* ── things sitting off the wall ───────────────────────────────────────────
   The backdrop is a lit surface with a light source in it, so the panels that
   float over it should cast something. Two shadows each rather than one: a
   tight dark one for contact, and a wide soft one for the distance — which is
   what an actual shadow from a soft source looks like, and what a single blur
   never quite reads as.

   Thrown down and to the RIGHT at roughly forty-five degrees, along the same
   line the light in these photographs travels: the bands come in over the
   top-left corner and run to the bottom-right, so anything standing off the
   wall throws its shadow the same way and about as far as it is tall.

   Sideways offset nearly equal to the downward one is what makes it read as
   an ANGLE rather than as height. A shadow sitting straight underneath says
   the light is overhead, which is the one thing these photographs are not —
   and it was reading as a soft edge on the panel rather than as a shadow on
   the wall behind it.

   Dark enough to survive the backdrop it lands on. The first pass sat around
   .26 and was invisible: these panels are dark themselves, and a soft dark
   shadow behind a dark panel on a mid-tone wall has almost nothing to be
   darker THAN. It reads on a pale backdrop and disappears on a warm one,
   which is the worse half of the range to lose. */
body.glass .bigsearch2 textarea,
body.glass .peek-card,
body.glass .acbox2 {
  box-shadow:
    0 2px 6px rgba(18, 14, 10, .42),
    22px 26px 44px rgba(18, 14, 10, .40),
    52px 60px 104px rgba(18, 14, 10, .30);
}
/* The field is the one you touch, so it lifts a little when you do. */
/* Lifting something moves its shadow further from it, and further along the
   same line — not simply darker in place. */
body.glass .bigsearch2 textarea:hover {
  box-shadow:
    0 3px 8px rgba(18, 14, 10, .44),
    28px 34px 54px rgba(18, 14, 10, .44),
    66px 76px 126px rgba(18, 14, 10, .32);
}
body.glass .bigsearch2 textarea:focus-visible {
  box-shadow:
    0 3px 9px rgba(18, 14, 10, .46),
    32px 38px 60px rgba(18, 14, 10, .46),
    74px 86px 140px rgba(18, 14, 10, .34);
}
body.glass .peek-card:hover {
  box-shadow:
    0 3px 9px rgba(18, 14, 10, .44),
    30px 36px 58px rgba(18, 14, 10, .44),
    70px 82px 134px rgba(18, 14, 10, .32);
}
/* The chip rail rides on the field's shadow, not its own. */
body.glass .barchip { box-shadow: 0 1px 3px rgba(24, 20, 16, .30); }

/* A past search reads as a line, not a name — it is often a whole sentence,
   so it wraps rather than being cut off at the edge. */
.ac-item.ac-past { align-items: flex-start; }
.ac-item.ac-past .nm { white-space: normal; overflow: visible; line-height: 1.4; }

/* ── the shadow on the floor ───────────────────────────────────────────────
   A box-shadow is always parallel to the thing casting it, so it can sit
   under a panel but never lie down on the ground behind it. This is the
   ground-plane shadow: a separate shape, skewed along the light and squashed
   the way a shadow is when it falls away from you across a floor.

   ::before rather than ::after, so it paints before the field and the panels
   do and therefore behind them, with no z-index needed anywhere. Ordering by
   the box model beats ordering by number when the numbers would have to be
   kept in step with the chips, the mirror, the list and the preview.

   skewX leans the bottom to the RIGHT because that is where the light throws
   it, and scaleY squashes it because a shadow running away along the ground
   is foreshortened. The blur is small and on a small element — the lesson
   from the backdrop filter is about full-screen work every frame, not about
   a 700px strip that never moves. */
body.glass .bigsearch2::before,
body.glass .peek::before {
  content: '';
  position: absolute;
  left: 3%; width: 97%;
  height: 78px;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(14, 10, 6, .62) 0%,
    rgba(14, 10, 6, .34) 42%,
    rgba(14, 10, 6, 0) 100%);
  border-radius: 999px / 30px;
  transform: skewX(30deg) scaleY(.82);
  transform-origin: top left;
  filter: blur(9px);
}
body.glass .bigsearch2::before { top: calc(100% - 10px); }
/* The preview's floor is the gap between it and the field, which is narrow,
   so its shadow is shorter and lands harder. */
body.glass .peek::before {
  top: calc(100% - 16px);
  height: 46px;
  background: linear-gradient(to bottom,
    rgba(14, 10, 6, .55) 0%,
    rgba(14, 10, 6, 0) 100%);
}

/* With a shadow on the ground doing the work, the shadow ON the panel goes
   back to being contact: close, tight, and no longer pretending to be the
   whole story. */
body.glass .bigsearch2 textarea,
body.glass .peek-card,
body.glass .acbox2 {
  box-shadow:
    0 2px 5px rgba(18, 14, 10, .40),
    14px 16px 30px rgba(18, 14, 10, .32);
}
body.glass .bigsearch2 textarea:hover,
body.glass .bigsearch2 textarea:focus-visible,
body.glass .peek-card:hover {
  box-shadow:
    0 2px 6px rgba(18, 14, 10, .44),
    18px 22px 38px rgba(18, 14, 10, .36);
}
/* Lifting it lengthens what it throws, which is the ground shadow's job. */
body.glass .bigsearch2:hover::before,
body.glass .peek:hover::before { transform: skewX(34deg) scaleY(.95); }
body.glass .bigsearch2::before,
body.glass .peek::before { transition: transform .18s ease; }

/* What the record will say about a stage that moved with nothing said. */
.peek-said {
  font-size: 12.5px; line-height: 1.5; color: #85898f;
  border-left: 3px solid rgba(200,102,58,.55); padding: 2px 0 2px 10px;
}
body.glass .peek-said { color: rgba(244,243,240,.62); }

/* What the record will say about a stage that moved with nothing said. */
.peek-said {
  font-size: 12.5px; line-height: 1.5; color: #85898f;
  border-left: 3px solid rgba(200,102,58,.55); padding: 2px 0 2px 10px;
}
body.glass .peek-said { color: rgba(244,243,240,.62); }

/* ── the cork board ────────────────────────────────────────────────────────
   Hung on the wall at a quarter size, and brought forward to about three
   quarters of the screen when opened — the room stays visible around it,
   because it is coming off a wall rather than replacing one.

   `container-type: inline-size` is what makes the notes survive the journey.
   Everything inside is sized in cqw, a percentage of the board's own width,
   so the whole thing is one drawing that grows and shrinks: a note pinned
   two thirds across while it is open is two thirds across when it is back on
   the wall, at a fifth of the size, still the shape you remember.

   No z-index while it is closed. It is a child of body, after the photograph
   and before the sky layer, so the drift, the passing cloud and the whole
   dawn wash over it in paint order exactly as they wash over the wall. That
   is also why it cannot be clicked directly — see .cork-hit. */
.corkboard {
  position: fixed;
  left: 7%; top: 17%;
  height: 21vh;
  aspect-ratio: 902 / 650;
  container-type: inline-size;
  background: url('img/corkboard.jpg') center/100% 100% no-repeat;
  border-radius: 3px;
  box-shadow:
    0 2px 5px rgba(18, 14, 10, .40),
    14px 16px 30px rgba(18, 14, 10, .34);
  transition:
    left .62s cubic-bezier(.32,.72,.2,1),
    top .62s cubic-bezier(.32,.72,.2,1),
    height .62s cubic-bezier(.32,.72,.2,1),
    box-shadow .62s ease,
    transform .62s cubic-bezier(.32,.72,.2,1);
  opacity: 0; pointer-events: none;
  visibility: hidden;
}
body.has-board .corkboard { opacity: 1; visibility: visible; }

/* Off the wall: forward, centred, and throwing a much longer shadow because
   it is no longer against anything. */
.corkboard.open {
  left: 50%; top: 14vh;
  height: 70vh;
  transform: translateX(-50%);
  z-index: 60;
  pointer-events: auto;
  box-shadow:
    0 8px 20px rgba(14, 10, 6, .46),
    40px 60px 120px rgba(14, 10, 6, .52);
}

/* The way in. Above the interface, which the board is not, with the same
   geometry so the two cannot disagree about where the board is. */
.cork-hit {
  position: fixed;
  left: 7%; top: 17%;
  height: 21vh;
  aspect-ratio: 902 / 650;
  z-index: 25;
  border: none; background: transparent; padding: 0;
  border-radius: 3px; cursor: pointer;
  opacity: 0; pointer-events: none;
  transition: box-shadow .2s ease;
}
body.has-board .cork-hit { opacity: 1; pointer-events: auto; }
body.has-board.board-open .cork-hit { pointer-events: none; }
.cork-hit:hover { box-shadow: 0 0 0 2px rgba(255,255,255,.55), 0 0 30px rgba(255,255,255,.30); }
.cork-hit:focus-visible { outline: 2px solid #c8663a; outline-offset: 3px; }

/* The light and shade of the wall, projected onto the board.

   The board was being TINTED by the light layers all along — force the cloud
   black and it goes dark with everything else. What it was not getting was the
   MODELLING: the bright bands and soft shadows that fall across the wall live
   in the photograph itself, and the board covers the photograph, so where it
   hangs there was nothing to cast anything on it. Evenly lit cork in front of
   a wall with rays running over it, which is exactly what "not in the room"
   looks like.

   `background-attachment: fixed` is the whole answer: it frames this copy of
   the photograph against the VIEWPORT rather than against the element, so the
   piece showing through the board is the piece that would have been behind it,
   in register, at the same scale. `overlay` then applies it as light rather
   than as a picture — dark where the wall is dark, bright where the rays fall,
   and the cork's own colour underneath.

   Only while it is on the wall. Once it is off the wall and forward, the wall
   is behind it and has no business falling across it. */
.corkboard::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: var(--backdrop-img, none);
  background-size: cover;
  background-position: center;
  background-attachment: fixed;
  mix-blend-mode: overlay;
  opacity: .72;
  transition: opacity .5s ease;
  border-radius: inherit;
}
.corkboard.open::after { opacity: 0; }
body.no-photo .corkboard::after { background-image: none; }

/* The cork inside the frame. The photograph's frame is about 3% of it. */
.cork-surface { position: absolute; inset: 3.4% 3% 3.6% 3%; }

.cork-tools {
  position: absolute; right: 2.4%; bottom: -3.6cqw;
  display: flex; gap: 1.2cqw;
  opacity: 0; pointer-events: none; transition: opacity .3s ease .25s;
}
.corkboard.open .cork-tools { opacity: 1; pointer-events: auto; }
.cork-btn {
  font: inherit; font-size: 2cqw; font-weight: 650;
  padding: 1cqw 2.2cqw; border-radius: 999px; cursor: pointer;
  border: 1px solid rgba(255,255,255,.22);
  background: rgba(28, 26, 23, .62); color: #f4f3f0;
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
}
.cork-btn:hover { background: rgba(28, 26, 23, .78); }
.cork-done { background: #c8663a; border-color: #c8663a; }
.cork-done:hover { background: #b95a30; }

/* ── a note ───────────────────────────────────────────────────────────── */
.cork-note {
  position: absolute;
  /* Width is set per note, in cqw, so it scales with the board like everything
     else. Height is deliberately NOT set: a note is as tall as what is written
     on it, the way paper is. */
  min-height: 9cqw;
  padding: 3cqw 1.6cqw 1.6cqw;
  transform: rotate(var(--rot, 0deg));
  box-shadow: 0.4cqw 0.8cqw 1.6cqw rgba(20, 14, 8, .34);
  font-size: 2.1cqw; line-height: 1.35;
  color: #2b2620;
  /* Closed, the whole board is one button; the notes are a picture of what is
     pinned to it and nothing on them is separately clickable. */
  pointer-events: none;
}
.corkboard.open .cork-note { pointer-events: auto; cursor: grab; }
.corkboard.open .cork-note.lifted {
  cursor: grabbing;
  transform: rotate(var(--rot, 0deg)) scale(1.04);
  box-shadow: 1cqw 2cqw 3.4cqw rgba(20, 14, 8, .44);
  z-index: 2;
}
.tint-ivory  { background: #f6f0e2; }
.tint-butter { background: #f7e9b8; }
.tint-rose   { background: #f6dcd6; }
.tint-sky    { background: #d9e6f2; }

.cork-pin {
  position: absolute; left: 50%; top: 1.1cqw; transform: translateX(-50%);
  width: 1.9cqw; height: 1.9cqw; border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #f0c85a, #a9761b 70%, #7d5411);
  box-shadow: 0 0.35cqw 0.6cqw rgba(20, 14, 8, .45);
}
/* Written straight onto the paper. A focus background is the convention for a
   field, and this is not one — it is a note, and the pen does not come with a
   white box under it. */
.cork-text { outline: none; min-height: 1.2em; white-space: pre-wrap; overflow-wrap: anywhere; }
/* The note is dragged; the words on it are written. Two different jobs on one
   piece of paper, and the pointer is the only thing that can say which is
   which before you press. */
.corkboard.open .cork-text { cursor: text; }

/* The kit rides above the note and only while the pointer is on it, so a
   quiet board stays a quiet board. Counter-rotated, because a row of controls
   pinned at three degrees reads as broken rather than as casual. */
.cork-kit {
  position: absolute; left: 0; top: -3.6cqw;
  display: flex; align-items: center; gap: 0.7cqw;
  padding: 0.5cqw 0.8cqw; border-radius: 999px;
  background: rgba(24, 20, 16, .74);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  transform: rotate(calc(var(--rot, 0deg) * -1));
  opacity: 0; pointer-events: none; transition: opacity .14s ease;
}
.cork-note:hover .cork-kit,
.cork-note:focus-within .cork-kit { opacity: 1; pointer-events: auto; }

.cork-tint {
  width: 2.3cqw; height: 2.3cqw; border-radius: 50%; padding: 0;
  border: 0.28cqw solid transparent; cursor: pointer;
}
.cork-tint.on { border-color: #f4f3f0; }

.cork-x {
  width: 2.6cqw; height: 2.6cqw; border-radius: 50%;
  border: none; cursor: pointer; padding: 0; margin-left: 0.4cqw;
  font-size: 2cqw; line-height: 1;
  background: rgba(255,255,255,.16); color: #f4f3f0;
}
.cork-x:hover { background: #b03636; }

/* The corner grip. Width only — the height follows the words. */
.cork-grip {
  position: absolute; right: -0.3cqw; bottom: -0.3cqw;
  width: 2.6cqw; height: 2.6cqw; cursor: ew-resize;
  background: linear-gradient(135deg, transparent 46%, rgba(40,32,22,.34) 46%,
              rgba(40,32,22,.34) 58%, transparent 58%,
              transparent 66%, rgba(40,32,22,.34) 66%, rgba(40,32,22,.34) 78%, transparent 78%);
  opacity: 0; transition: opacity .14s ease;
}
.cork-note:hover .cork-grip { opacity: 1; }

/* With the board forward, the bar is behind it and would only be in the way. */
body.board-open .bigsearch2, body.board-open .picker2 { opacity: 0; pointer-events: none; }
body.board-open .bigsearch2, body.board-open .picker2 { transition: opacity .3s ease; }

@media (prefers-reduced-motion: reduce) {
  .corkboard { transition: none; }
}

/* ── the weather dials ────────────────────────────────────────────────────
   A range input rather than a drawn knob. A knob would suit the room better
   and would be worse to use: a slider is draggable, clickable, arrow-keyed
   and readable by a screen reader without any of that being written, and the
   thing being adjusted is right behind the panel anyway — the preview is the
   whole window, so the control does not have to look like anything. */
.dialrow { align-items: center; gap: var(--space-4); }
.dial-why { opacity: .72; }
.dial {
  flex: 0 0 auto; width: 190px; max-width: 42vw; height: 26px;
  -webkit-appearance: none; appearance: none;
  background: transparent; cursor: pointer;
}
.dial::-webkit-slider-runnable-track {
  height: 6px; border-radius: 999px;
  background: linear-gradient(90deg, #6f7d99 0%, #cbbba4 52%, #c8663a 100%);
}
.dial::-moz-range-track {
  height: 6px; border-radius: 999px;
  background: linear-gradient(90deg, #6f7d99 0%, #cbbba4 52%, #c8663a 100%);
}
.dial::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 20px; height: 20px; margin-top: -7px;
  border-radius: 50%; background: #fbfaf8;
  border: 1px solid rgba(28,30,33,.22);
  box-shadow: 0 1px 4px rgba(20,18,16,.34);
}
.dial::-moz-range-thumb {
  width: 20px; height: 20px; border-radius: 50%; background: #fbfaf8;
  border: 1px solid rgba(28,30,33,.22);
  box-shadow: 0 1px 4px rgba(20,18,16,.34);
}
.dial:focus-visible { outline: 2px solid #c8663a; outline-offset: 4px; }

/* ── the workload dial ────────────────────────────────────────────────────
   Centre bottom of the search screen, anchored to the window rather than to
   the row: it is about the whole of your work, not about whatever is on
   screen. Same fan as the shortcut icons above it, so there is one gesture in
   this app for "a button with more inside it".

   The ring is the point. Three arcs sized by how much of your work sits in
   each state, in the colours the work moves through — waiting, underway,
   done. You can read your week off it without reading anything. */
.dial-wrap {
  /* Bottom RIGHT, not bottom centre. Centred, the fan opened straight up
     into the search bar — which is the one thing on this screen that must
     never have anything in front of it. Out here the arc has the whole
     corner to open into and crosses nothing. */
  position: fixed; right: 44px; bottom: 26px;
  z-index: 24; display: flex; flex-direction: column; align-items: center;
}
.dial {
  position: relative; width: 64px; height: 64px; padding: 0;
  border: none; background: none; cursor: pointer;
  display: grid; place-items: center;
  filter: drop-shadow(0 6px 16px rgba(18,14,10,.42));
  transition: transform .2s cubic-bezier(.2,.9,.28,1.25);
}
.dial-wrap:hover .dial, .dial-wrap:focus-within .dial { transform: scale(1.08); }
.dial > svg { position: absolute; inset: 0; width: 64px; height: 64px;
              transform: rotate(-90deg);   /* start the first arc at twelve */
              transition: opacity .22s ease; }
.dial-arc {
  fill: none; stroke-width: 7; stroke-linecap: butt;
  /* The glow lives in #dialglow, on the group — see index.html for why it
     cannot be a CSS drop-shadow here. */
  transition: stroke-dasharray .5s cubic-bezier(.32,.72,.2,1),
              stroke-dashoffset .5s cubic-bezier(.32,.72,.2,1);
}
.dial::before {
  content: ''; position: absolute; inset: 9px; border-radius: 50%;
  background: rgba(252, 251, 249, .96);
  box-shadow: inset 0 1px 2px rgba(20,18,16,.14);
  transition: opacity .22s ease;
}
.dial-n {
  position: relative; font-weight: 700; font-size: 17px; color: #2b2620;
  letter-spacing: -.01em;
  transition: opacity .22s ease;
}

/* What the coloured ring turns into while the fan is open: the same ring,
   white and half there. It sits exactly where the arcs are — inset 2.5px on a
   64px button puts a 7px border on top of a stroke of 7 at r=26 — so the one
   fades into the other rather than one vanishing and another appearing
   somewhere near it.

   It is here because the spokes need somewhere to come from. With the arcs
   simply gone, three lines converged on a patch of empty wall; this is the
   hub the sketch had, drawn as a ring rather than a dot. */
.dial::after {
  content: ''; position: absolute; inset: 2.5px; border-radius: 50%;
  border: 7px solid rgba(255, 255, 255, .26);
  opacity: 0; transition: opacity .22s ease;
  pointer-events: none;
}
.dial-wrap:hover .dial::after, .dial-wrap:focus-within .dial::after { opacity: 1; }

/* Reaching for it clears it away entirely — disc, number and ring.

   At rest the dial answers two questions at a glance: how much is left, and
   what shape it is in. Once the fan is open you are past both of those and on
   your way somewhere, and the three arcs are then just a small bright thing
   sitting underneath three larger ones saying the same words. So the whole
   button goes, and what is left is three lines converging on where it was.

   Keyed off the wrapper, so it clears the moment the menu opens rather than
   only while the pointer is on the button itself. */
.dial-wrap:hover .dial::before, .dial-wrap:focus-within .dial::before,
.dial-wrap:hover .dial-n,       .dial-wrap:focus-within .dial-n,
.dial-wrap:hover .dial > svg,   .dial-wrap:focus-within .dial > svg { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .dial::before, .dial::after, .dial-n, .dial > svg { transition: none; }
}

/* The fan opens up and to the LEFT — into the room, away from both edges it
   is tucked against. Angles are set per item in the markup. */
.dial-radial { top: 4px; }
.dial-radial .ri-btn { border-color: var(--tint); }

/* No dial behind the buttons. The faint ring was there to make a loose fan of
   icons read as one control; the spokes do that job better, because each one
   also says WHICH control it is leading to. */
.dial-radial .radial-ring { display: none; }

.dial-spokes {
  position: absolute; left: 0; top: 0;
  width: 380px; height: 380px;
  transform: translate(-50%, -50%);
  overflow: visible; pointer-events: none;
  opacity: 0; transition: opacity .2s ease .04s;
}
.dial-wrap:hover .dial-spokes, .dial-wrap:focus-within .dial-spokes { opacity: 1; }
.dial-spokes line {
  stroke-width: 7; stroke-linecap: round;
  /* Grow out of the hub rather than appearing whole, in step with the
     buttons travelling out along the same lines. */
  transform-origin: 0 0;
  transform: scale(.12);
  transition: transform .34s cubic-bezier(.2,.9,.28,1.25);
}
.dial-wrap:hover .dial-spokes line,
.dial-wrap:focus-within .dial-spokes line { transform: scale(1); }

/* The label goes wherever its own spoke is not. Absolute, so the item box
   stays the size of its button and the arithmetic that places the button on
   the arc is untouched. */
.dial-radial .radial-item { width: 46px; }
.dial-radial .ri-label {
  position: absolute; white-space: nowrap; width: auto;
  text-shadow: 0 1px 4px rgba(14,12,10,.75);
}
.ri-at-left .ri-label  { right: calc(100% + 12px); top: 50%; transform: translateY(-50%); }
.ri-at-upleft .ri-label { right: calc(100% + 4px); bottom: calc(100% + 4px); text-align: right; }
.ri-at-top .ri-label   { bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%); }

/* The count rides on the button rather than sitting under the label. Below
   it, two lines of label pushed it so far down that it read as a separate
   thing floating on the wall rather than as this button's number. */
.ri-count {
  position: absolute; top: -5px; left: calc(50% + 13px);
  min-width: 19px; height: 19px; padding: 0 5px;
  display: grid; place-items: center;
  border-radius: 999px; background: var(--tint, #56514a);
  color: #fff; font-size: 11.5px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 2px 6px rgba(14,12,10,.45);
}
.ri-count.none { background: rgba(90, 85, 78, .9); }

/* ── the task screen ────────────────────────────────────────────────────── */
.taskswitch { display: flex; gap: 8px; margin: 0 0 var(--space-4); flex-wrap: wrap; }
.taskpill {
  display: inline-flex; align-items: center; gap: 8px;
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  padding: 8px 14px; border-radius: 999px;
  border: 1px solid var(--hair); background: transparent; color: var(--color-text);
}
.taskpill:hover { border-color: var(--tint); }
.taskpill.on { border-color: var(--tint); background: color-mix(in srgb, var(--tint) 16%, transparent); }
.taskpill-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--tint); }
.taskpill-n { opacity: .6; font-variant-numeric: tabular-nums; }

.taskadd { padding: var(--space-4); }
.tasklist { display: flex; flex-direction: column; gap: 8px; }
.taskrow {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 12px var(--space-4) 12px 0;
  border: 1px solid var(--hair); border-radius: 12px;
  background: var(--color-surface, rgba(255,255,255,.04));
  overflow: hidden;
}
.taskrow-bar { width: 4px; align-self: stretch; background: var(--tint); flex: 0 0 auto; }
.taskrow-main { flex: 1; min-width: 0; padding-left: var(--space-3); }
.taskrow-title { font-weight: 600; }
.taskrow-sub { font-size: 13px; color: var(--color-neutral-600); margin-top: 2px; }
.taskrow-acts { display: flex; gap: 6px; flex: 0 0 auto; }

/* ── the wall ──────────────────────────────────────────────────────────────
   Things hung in the photograph. Each is positioned as a share of the
   viewport and sized from its height, so the arrangement survives a different
   screen without anything being measured.

   No z-index while they are on the wall. They are children of body, after the
   photograph and before the sky layer, so the drift, the cloud and the whole
   dawn wash over them in paint order exactly as they wash over the wall. That
   is also why they cannot be clicked directly — see .wall-hits. */
.wall, .wall-hits { position: fixed; inset: 0; pointer-events: none; }
.wall-hits { z-index: 25; }
body.ws-hidden .wall, body.ws-hidden .wall-hits { display: none; }
.wall, .wall-hits { opacity: 0; visibility: hidden; transition: opacity .4s ease; }
body.has-wall .wall, body.has-wall .wall-hits { opacity: 1; visibility: visible; }

/* They leave and arrive with the rest of the interface, so signing out takes
   the room apart in one movement rather than leaving a board on a black wall. */
body.ui-arriving .wall { animation: uiArrive 4s ease-out 2s 1 both; }
body.ui-leaving .wall, body.ui-leaving .wall-hits { animation: uiLeave 2s ease-in 1 both; }

.wsi {
  position: absolute;
  height: var(--h); aspect-ratio: var(--ratio);
  container-type: inline-size;
  pointer-events: none;
  transition:
    left .62s cubic-bezier(.32,.72,.2,1), top .62s cubic-bezier(.32,.72,.2,1),
    height .62s cubic-bezier(.32,.72,.2,1), transform .62s cubic-bezier(.32,.72,.2,1),
    filter .5s ease;
}

/* The light and shade of the wall, projected onto whatever hangs on it.

   These things were being TINTED by the light layers all along — force the
   cloud black and they go dark with everything else. What they were not
   getting was the MODELLING: the bright bands and soft shadows live in the
   photograph, and an object covering the photograph has nothing to cast
   anything on it. Evenly lit cork in front of a wall with rays running over
   it, which is what "not in the room" looks like.

   `background-attachment: fixed` is the whole answer. It frames this copy of
   the photograph against the VIEWPORT rather than the element, so the piece
   showing through is the piece that would have been behind it, in register
   and at the same scale. `overlay` applies it as light rather than as a
   picture. Gone once the thing is off the wall and forward, where the wall is
   behind it and has no business falling across it. */
.wsi::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: var(--backdrop-img, none);
  background-size: cover; background-position: center;
  background-attachment: fixed;
  mix-blend-mode: overlay; opacity: .72;
  transition: opacity .5s ease;
  border-radius: inherit;
}
.wsi.open::after { opacity: 0; }
body.no-photo .wsi::after { background-image: none; }

/* The wall itself comes forward while something is open on it.

   .wsi.open carries z-index 60, and that was doing nothing outside its own
   box: .wall is position:fixed, and a fixed element establishes a stacking
   context whatever its z-index, so 60 only ever ordered the open item against
   its SIBLINGS. The wall as a whole stayed at auto, underneath the interface —
   which is why a click on "+ Note" landed on .home2-body and the board, the
   planner and the phone all felt dead. Nothing on the wall can be reached
   until the wall is in front.

   Only while something is open. The rest of the time the wall belongs behind
   everything, which is what makes it a wall. */
body.ws-open .wall { z-index: 60; }

/* And the light comes with it. The two wash layers — the warm one and the
   cloud — normally paint just above the wall and just below the interface,
   which is what puts the room's colour on everything hanging in it. Raising
   the wall past the interface would have left the open item lit by nothing,
   looking like a panel rather than a thing picked up off a wall. They carry
   pointer-events: none, so putting them in front blocks nothing. */
body.ws-open.glass::after,
body.ws-open .skylight { z-index: 61; }

.wsi.open {
  left: 50% !important; top: 13vh !important;
  height: 72vh;
  transform: translateX(-50%);
  z-index: 60;
  pointer-events: auto;
}
/* Everything else on the wall steps back while one thing is forward. */
body.ws-open .wsi:not(.open) { filter: brightness(.62) saturate(.8); }

/* One thing forward puts the search bar away. It is the same screen, so the
   bar is still there under the board and still takes the keystrokes meant for
   a sticky note — and it was drawing itself straight across the middle of an
   open board. Nothing to solve; it just steps out while you are at the wall. */
body.ws-open .bigsearch2 {
  opacity: 0; pointer-events: none;
  transition: opacity .28s ease;
}

/* Carried, and put back.

   A carried thing must not lag the pointer, so its own easing comes off for
   the length of the drag. The return is a movement rather than a snap, but a
   quick one — long enough to read as "back where it was", short enough that
   it is plainly a refusal and not a second animation to sit through. */
.wsi.carrying, .wsi-hit.carrying { transition: none; }
.wsi.carrying { cursor: grabbing; }
.wsi.returning, .wsi-hit.returning {
  transition: left .24s cubic-bezier(.4,.02,.2,1), top .24s cubic-bezier(.4,.02,.2,1);
}
.wsi-hit { cursor: grab; }

/* The two places it may hang, drawn only while something is in the air.

   Three states, because there are three things worth knowing. Pale is "here
   is allowed". Lit is "let go and it stays where it is". Outlined is "let go
   and it comes HERE" — shown while the thing is over ground it cannot have,
   so the small correction that follows is announced rather than sprung. */
.ws-zone {
  position: fixed; pointer-events: none; border-radius: 10px;
  border: 1px dashed rgba(255,255,255,.34);
  background: rgba(255,255,255,.05);
  transition: background .16s ease, border-color .16s ease, box-shadow .16s ease;
}
.ws-zone.taking {
  border: 1px solid rgba(255,236,214,.72);
  background: rgba(255,232,206,.13);
  box-shadow: inset 0 0 40px rgba(255,225,190,.16);
}
.ws-zone.aiming {
  border: 1px dashed rgba(255,236,214,.62);
  background: rgba(255,232,206,.06);
}

.wsi-hit {
  position: absolute;
  height: var(--h); aspect-ratio: var(--ratio);
  border: none; background: transparent; padding: 0;
  border-radius: 4px; cursor: pointer; pointer-events: auto;
  transition: box-shadow .2s ease;
}
.wsi-hit:hover { box-shadow: 0 0 0 2px rgba(255,255,255,.5), 0 0 30px rgba(255,255,255,.28); }
.wsi-hit:focus-visible { outline: 2px solid #c8663a; outline-offset: 3px; }

/* The tools are NOT part of the drawing, so they are not measured in cqw.

   Everything inside an item scales with the item, which is the whole trick —
   a note two thirds across a board is two thirds across at any size. The
   controls around it are the opposite: they are the interface, and they want
   to be the same size on a wide board and on a phone. In cqw they were not.
   The phone is about 280px across when it is open, so 1.9cqw made "Done" five
   pixels tall and unclickable, sitting on top of the send button. */
.wsi-tools {
  position: absolute; right: 0; bottom: -46px;
  display: flex; gap: 8px;
  opacity: 0; pointer-events: none; transition: opacity .3s ease .25s;
}
.wsi.open .wsi-tools { opacity: 1; pointer-events: auto; }
.ws-btn {
  font: inherit; font-size: 14px; font-weight: 650; line-height: 1.2;
  padding: 9px 18px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  border: 1px solid rgba(255,255,255,.22);
  background: rgba(28, 26, 23, .64); color: #f4f3f0;
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
}
.ws-btn:hover { background: rgba(28, 26, 23, .8); }
.ws-done { background: #c8663a; border-color: #c8663a; }
.ws-done:hover { background: #b95a30; }

/* ── decor ────────────────────────────────────────────────────────────────
   A plant standing on the floor. Two things make it stand rather than hang.

   Its `y` is the line where the pot meets the floor rather than its top
   edge, so coming forward makes it bigger without its feet sliding, and the
   top is worked out from the base and the height. That happens in JS,
   because the height is what the base decides.

   And the wall's light is masked to the plant's own silhouette. The overlay
   every hung thing carries is a rectangle, which is invisible on a board
   because a board IS a rectangle — over a cut-out it would paint a lit
   rectangle in the air around the leaves. */
.wsi-plant {
  background: url('img/plant.png') center bottom / contain no-repeat;
}
.wsi-plant::after {
  -webkit-mask-image: url('img/plant.png'); mask-image: url('img/plant.png');
  -webkit-mask-size: 100% 100%;            mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;          mask-repeat: no-repeat;
  opacity: .62;
}
/* Nothing to step back from — decor never comes forward, so it never dims. */
body.ws-open .wsi-plant { filter: none; }
.wsm-plant { background: #4d7355; }

/* ── board ────────────────────────────────────────────────────────────── */
.wsi-board {
  background: url('img/corkboard.jpg') center/100% 100% no-repeat;
  border-radius: 3px;
  box-shadow: 0 2px 5px rgba(18,14,10,.40), 14px 16px 30px rgba(18,14,10,.34);
}
.wsi-board.open { box-shadow: 0 8px 20px rgba(14,10,6,.46), 40px 60px 120px rgba(14,10,6,.5); }
.cork-surface { position: absolute; inset: 3.4% 3% 3.6% 3%; }

.cork-note {
  position: absolute; min-height: 9cqw;
  padding: 3cqw 1.6cqw 1.6cqw;
  transform: rotate(var(--rot, 0deg));
  box-shadow: .4cqw .8cqw 1.6cqw rgba(20,14,8,.34);
  font-size: 2.1cqw; line-height: 1.35; color: #2b2620;
  pointer-events: none;
}
.wsi.open .cork-note { pointer-events: auto; cursor: grab; }
.wsi.open .cork-note.lifted {
  cursor: grabbing; z-index: 2;
  transform: rotate(var(--rot, 0deg)) scale(1.04);
  box-shadow: 1cqw 2cqw 3.4cqw rgba(20,14,8,.44);
}
.tint-ivory { background: #f6f0e2; } .tint-butter { background: #f7e9b8; }
.tint-rose  { background: #f6dcd6; } .tint-sky    { background: #d9e6f2; }

.cork-pin {
  position: absolute; left: 50%; top: 1.1cqw; transform: translateX(-50%);
  width: 1.9cqw; height: 1.9cqw; border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #f0c85a, #a9761b 70%, #7d5411);
  box-shadow: 0 .35cqw .6cqw rgba(20,14,8,.45);
}
/* Written straight onto the paper: a focus background is the convention for a
   field, and a note is not one. The note is dragged, the words are written,
   and the pointer is the only thing that can say which before you press. */
.cork-text { outline: none; min-height: 1.2em; white-space: pre-wrap; overflow-wrap: anywhere; }
.wsi.open .cork-text { cursor: text; }

.cork-kit {
  position: absolute; left: 0; top: -3.6cqw;
  display: flex; align-items: center; gap: .7cqw;
  padding: .5cqw .8cqw; border-radius: 999px;
  background: rgba(24,20,16,.74);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  transform: rotate(calc(var(--rot, 0deg) * -1));
  opacity: 0; pointer-events: none; transition: opacity .14s ease;
}
.cork-note:hover .cork-kit, .cork-note:focus-within .cork-kit { opacity: 1; pointer-events: auto; }
.cork-tint { width: 2.3cqw; height: 2.3cqw; border-radius: 50%; padding: 0;
             border: .28cqw solid transparent; cursor: pointer; }
.cork-tint.on { border-color: #f4f3f0; }
.cork-x { width: 2.6cqw; height: 2.6cqw; border-radius: 50%; border: none; padding: 0;
          margin-left: .4cqw; font-size: 2cqw; line-height: 1; cursor: pointer;
          background: rgba(255,255,255,.16); color: #f4f3f0; }
.cork-x:hover { background: #b03636; }
.cork-grip {
  position: absolute; right: -.3cqw; bottom: -.3cqw;
  width: 2.6cqw; height: 2.6cqw; cursor: ew-resize;
  background: linear-gradient(135deg, transparent 46%, rgba(40,32,22,.34) 46%,
    rgba(40,32,22,.34) 58%, transparent 58%, transparent 66%,
    rgba(40,32,22,.34) 66%, rgba(40,32,22,.34) 78%, transparent 78%);
  opacity: 0; transition: opacity .14s ease;
}
.cork-note:hover .cork-grip { opacity: 1; }

/* ── planner ──────────────────────────────────────────────────────────── */
.wsi-calendar {
  background: #f4efe4; border-radius: 2px;
  box-shadow: 0 2px 5px rgba(18,14,10,.40), 14px 16px 30px rgba(18,14,10,.34);
}
.wsi-calendar.open { box-shadow: 0 8px 20px rgba(14,10,6,.46), 40px 60px 120px rgba(14,10,6,.5); }
.cal-sheet { position: absolute; inset: 4% 4.5%; display: flex; flex-direction: column; }
.cal-head { font-size: 3.1cqw; font-weight: 700; letter-spacing: .02em;
            color: #2b2620; margin-bottom: 1.2cqw; }
.cal-week { flex: 1; display: grid; grid-template-columns: repeat(7, 1fr);
            gap: .5cqw; min-height: 0; }
.cal-day { border: .1cqw solid #ddd3c0; border-radius: .4cqw;
           display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
.cal-dayhead {
  display: flex; align-items: baseline; gap: .5cqw;
  padding: .5cqw .6cqw .3cqw;
  border-bottom: .1cqw solid #e7dfcf;
}
.cal-dow { font-size: 1.25cqw; font-weight: 700; letter-spacing: .08em;
           text-transform: uppercase; color: #8a7f6d; }
.cal-num { font-size: 1.7cqw; font-weight: 650; color: #6d6252; margin-left: auto; }
.cal-today { border-color: rgba(200,102,58,.55); }
.cal-today .cal-dayhead { background: rgba(200,102,58,.13); }
.cal-today .cal-dow, .cal-today .cal-num { color: #b95a30; }

/* The whole rest of the column is the writing area, so a day is somewhere to
   put four things rather than a cell to put a word in. It fills the column
   even when it is empty — an editable region you have to aim at is one nobody
   uses, and the point of a planner on the wall is that writing on it is the
   easy part. */
.cal-mark {
  flex: 1; min-height: 0; overflow: auto;
  font-size: 1.35cqw; line-height: 1.35; color: #2b2620;
  padding: .5cqw .6cqw; outline: none;
}
.wsi.open .cal-mark { cursor: text; }
.wsi.open .cal-mark:hover { background: rgba(43,38,32,.045); }
.wsi.open .cal-mark:focus { background: rgba(200,102,58,.07); }

/* ── phone ────────────────────────────────────────────────────────────── */
/* The same thread, across the room.

   A phone on the wall is about 120px wide, and no honest amount of tuning
   makes a sentence readable at that size. What IS readable is that something
   arrived: the name of who it is with, and a stack of bubbles whose shape
   changes when a new one lands. So the wall copy keeps the shape and drops
   the reading — one line each, clipped, larger than the open phone's type
   relative to its width, with the newest carrying the accent. To read it you
   pick the phone up, which is also true of a phone. */
/* `.away`, not `.wall` — the room's own container is `.wall`, and it is
   `position: fixed; inset: 0`. A thread that also answered to that selector
   became the whole viewport and dropped its bubbles at the bottom of the
   screen. Modifier classes on a component want the component's own prefix
   for exactly this reason. */
.phone-thread.away {
  padding: 3cqw 3cqw 4cqw; gap: 2.4cqw; overflow: hidden;
  justify-content: flex-end;
}
.phone-thread.away .phone-bubble {
  font-size: 5cqw; line-height: 1.2; padding: 2.4cqw 3.4cqw; max-width: 88%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.phone-thread.away .phone-ack { font-size: 4cqw; }
.phone-thread.away .phone-bubble:last-child {
  box-shadow: 0 0 0 .6cqw rgba(200,102,58,.35);
}
.wsi-chat { pointer-events: none; }
.phone-shell {
  position: absolute; inset: 0; border-radius: 9cqw;
  background: linear-gradient(150deg, #3b3f45, #1b1e22 60%, #2a2e33);
  padding: 1.6cqw;
  box-shadow: 0 2px 5px rgba(18,14,10,.44), 14px 16px 30px rgba(18,14,10,.4);
}
.wsi-chat.open .phone-shell {
  box-shadow: 0 8px 20px rgba(14,10,6,.5), 40px 60px 120px rgba(14,10,6,.55);
}
.phone-glass {
  position: absolute; inset: 1.6cqw; border-radius: 7.4cqw; overflow: hidden;
  background: #14171b; display: flex; flex-direction: column;
  color: #f4f3f0;
}
.phone-lock { padding: 9cqw 5cqw; text-align: center; }
.phone-time { font-size: 17cqw; font-weight: 300; line-height: 1; }
.phone-sub { font-size: 4cqw; color: rgba(244,243,240,.6); margin-bottom: 6cqw; }
.phone-peek {
  text-align: left; background: rgba(255,255,255,.09); border-radius: 3.4cqw;
  padding: 2.6cqw 3cqw; margin-bottom: 2cqw; font-size: 3.5cqw;
}
.phone-peek.lit { background: rgba(200,102,58,.3); }
.phone-peek b { display: block; }
.phone-peek span { color: rgba(244,243,240,.66);
                   display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                   overflow: hidden; }

.phone-bar { display: flex; align-items: center; gap: 2cqw; padding: 4cqw 4cqw 2.4cqw;
             border-bottom: 1px solid rgba(255,255,255,.12); }
.phone-back { background: none; border: none; color: #f4f3f0; font-size: 6cqw;
              line-height: 1; cursor: pointer; padding: 0 1cqw; }
.phone-who { font-size: 4.2cqw; font-weight: 650; }
.phone-list { flex: 1; overflow-y: auto; }
.phone-row { display: block; width: 100%; text-align: left; cursor: pointer;
             background: none; border: none; color: inherit; font: inherit;
             padding: 3cqw 4cqw; border-bottom: 1px solid rgba(255,255,255,.08);
             font-size: 3.6cqw; position: relative; }
.phone-row:hover { background: rgba(255,255,255,.07); }
.phone-row b { display: block; margin-bottom: .6cqw; }
.phone-last { color: rgba(244,243,240,.6); display: block; overflow: hidden;
              text-overflow: ellipsis; white-space: nowrap; }
.phone-dot { position: absolute; right: 4cqw; top: 3cqw; background: #c8663a;
             border-radius: 999px; padding: 0 2cqw; font-size: 3cqw; font-weight: 700; }
.phone-thread { flex: 1; overflow-y: auto; padding: 3cqw; display: flex;
                flex-direction: column; gap: 1.8cqw; }
.phone-bubble { max-width: 82%; align-self: flex-start; font-size: 3.5cqw;
                line-height: 1.35; padding: 2.2cqw 3cqw; border-radius: 4cqw;
                background: rgba(255,255,255,.12); }
.phone-bubble.mine { align-self: flex-end; background: #c8663a; }
.phone-ack { align-self: center; font-size: 2.8cqw; color: rgba(244,243,240,.5); }
.phone-compose { display: flex; gap: 1.6cqw; padding: 2.6cqw 3cqw 3.4cqw;
                 border-top: 1px solid rgba(255,255,255,.12); }
.phone-input { flex: 1; min-width: 0; font: inherit; font-size: 3.4cqw;
               padding: 2.2cqw 3cqw; border-radius: 999px;
               border: 1px solid rgba(255,255,255,.18);
               background: rgba(255,255,255,.08); color: #f4f3f0; }
.phone-input::placeholder { color: rgba(244,243,240,.45); }
.phone-send { border: none; cursor: pointer; border-radius: 50%;
              width: 9cqw; height: 9cqw; font-size: 4cqw;
              background: #c8663a; color: #fff; }

/* ── the menu ─────────────────────────────────────────────────────────── */
.wsmenu {
  position: fixed; right: 2vw; top: 62px; z-index: 80;
  width: 268px; padding: 6px; border-radius: 16px;
  background: rgba(30, 28, 25, .93);
  -webkit-backdrop-filter: blur(22px) saturate(1.2); backdrop-filter: blur(22px) saturate(1.2);
  border: 1px solid rgba(255,255,255,.14);
  box-shadow: 0 3px 10px rgba(18,14,10,.5), 18px 30px 70px rgba(18,14,10,.55);
  color: #f4f3f0; font-size: 14px;
}
.wsm-row { display: flex; align-items: center; justify-content: space-between; gap: 8px;
           width: 100%; text-align: left; cursor: pointer; font: inherit; font-size: 14px;
           background: none; border: none; color: inherit;
           padding: 9px 11px; border-radius: 10px; }
.wsm-row:hover, .wsm-row.on { background: rgba(255,255,255,.1); }
.wsm-note { font-size: 11.5px; color: rgba(244,243,240,.5); }
.wsm-chev { color: rgba(244,243,240,.55); }
.wsm-sep { height: 1px; background: rgba(255,255,255,.12); margin: 5px 8px; }
.wsm-head { font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
            color: rgba(244,243,240,.45); padding: 5px 11px 3px; }
.wsm-item { display: flex; align-items: center; }
.wsm-name { flex: 1; display: flex; align-items: center; gap: 9px; text-align: left;
            cursor: pointer; font: inherit; font-size: 14px; background: none;
            border: none; color: inherit; padding: 8px 11px; border-radius: 10px; }
.wsm-name:hover { background: rgba(255,255,255,.1); }
.wsm-kind { width: 14px; height: 11px; border-radius: 2px; flex: 0 0 auto; }
.wsm-board { background: #b5813f; }
.wsm-calendar { background: #e6ddcb; }
.wsm-chat { background: #4d5560; width: 8px; height: 14px; border-radius: 2px; }
.wsm-drop { background: none; border: none; color: rgba(244,243,240,.4); cursor: pointer;
            font-size: 16px; line-height: 1; padding: 6px 9px; border-radius: 8px; }
.wsm-drop:hover { background: #b03636; color: #fff; }
.wsm-empty { padding: 8px 11px 10px; font-size: 13px; color: rgba(244,243,240,.5); }
.wsm-sub { padding: 2px 0 2px 10px; margin-left: 6px;
           border-left: 2px solid rgba(200,102,58,.55); }

/* ── documents ──────────────────────────────────────────────────────────
   The folder, and the page you write on. */

.docmake { margin-bottom: var(--space-5); }

.doclist { display: flex; flex-direction: column; gap: 8px; }
/* Each row is its own card, like a task. These screens float over the room
   and a list with no ground under it is a list you read the wallpaper
   through — legible in a screenshot, not legible at four in the afternoon. */
.docrow {
  display: grid; grid-template-columns: 34px minmax(0, 1fr) 28px;
  align-items: center; gap: var(--space-3);
  width: 100%; text-align: left; cursor: pointer; font: inherit; color: inherit;
  border: 1px solid var(--color-divider); border-radius: 12px;
  background: var(--color-surface);
  padding: 12px var(--space-4);
}
.docrow:hover, .docrow:focus-visible { background: var(--color-neutral-100); }
body.glass .docrow:hover, body.glass .docrow:focus-visible {
  background: var(--glass-strong);
}
.docrow:focus-visible { outline: 2px solid #c8663a; outline-offset: -2px; }
.docrow-ico svg {
  width: 21px; height: 21px; fill: none; stroke: var(--color-neutral-600);
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.docrow-main { display: block; min-width: 0; }
.docrow-title {
  display: block; font-weight: 600; font-size: 14.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.docrow-sub { display: block; font-size: 12.5px; color: var(--color-neutral-600); }
.docrow-drop {
  display: grid; place-items: center; width: 24px; height: 24px;
  border-radius: 999px; color: var(--color-neutral-600); font-size: 15px;
}
.docrow-drop:hover { background: var(--color-neutral-200); color: var(--color-text); }

/* The button on a case header. An icon and a word: the icon alone was one
   more thing to learn, and the word alone did not look like a place. */
.folderbtn { display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0; }
.folderbtn svg {
  width: 16px; height: 16px; fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.ch-top {
  display: flex; align-items: center; gap: var(--space-3);
  flex-wrap: wrap; margin-bottom: 4px;
}
.ch-top h2 { margin: 0; }

/* ── the editor ─────────────────────────────────────────────────────── */
.dochead {
  display: flex; align-items: center; gap: var(--space-3);
  flex-wrap: wrap; margin-bottom: 2px;
}
.docname {
  flex: 1; min-width: 200px;
  font-size: 21px; font-weight: 600; padding: 6px 10px;
  border-color: transparent; background: transparent;
}
.docname:hover { border-color: var(--color-divider); }
.docname:focus { border-color: var(--color-accent); background: var(--color-surface); }
.dochead-acts { display: flex; align-items: center; gap: var(--space-3); }
.docsaved { font-size: 12.5px; color: var(--color-neutral-600); white-space: nowrap; }
.docmeta {
  font-size: 13px; color: var(--color-neutral-600);
  margin: 0 0 var(--space-4) 10px;
}

.doctools {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  padding: 8px 10px; border: 1px solid var(--color-divider);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  background: var(--color-neutral-100);
  /* Stays put while the page scrolls under it. Formatting is applied to a
     selection, and a toolbar that scrolls away takes the selection's only
     controls with it just as somebody reaches for them. */
  position: sticky; top: 0; z-index: 5;
}
.docgroup {
  display: flex; align-items: center; gap: 4px;
  padding-right: var(--space-2);
}
.docgroup + .docgroup { border-left: 1px solid var(--color-divider); padding-left: var(--space-2); }
.docgroup-page { margin-left: auto; }
.doclab { font-size: 11.5px; color: var(--color-neutral-600); }
.docsel { width: auto; padding: 4px 22px 4px 8px; font-size: 12.5px; height: 28px; }
.docbtn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 30px;
  padding: 0 7px; cursor: pointer; font: inherit; font-size: 13.5px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-neutral-800);
}
.docbtn:focus-visible, .docsel:focus-visible {
  outline: 2px solid var(--color-accent, #c8663a); outline-offset: 1px;
}
/* A finger is not a mouse pointer. 44px is the floor every mobile guideline
   agrees on, and a toolbar is nothing but small targets side by side. */
@media (pointer: coarse) {
  .docbtn { min-width: 44px; height: 44px; }
  .dsw { width: 34px; height: 34px; }
  .dswatches { grid-template-columns: repeat(5, 34px); }
}
.docbtn:hover { background: var(--color-neutral-200); }
.docbtn:active { background: var(--color-neutral-300); }
/* The four alignment buttons are one glyph with four rules drawn on it.
   Four different characters would have been four different widths. */
.docbtn[class*="align-"] { position: relative; font-size: 0; }
.docbtn[class*="align-"]::before,
.docbtn[class*="align-"]::after {
  content: ''; position: absolute; left: 7px; right: 7px; height: 1.6px;
  background: currentColor; border-radius: 1px;
}
.docbtn[class*="align-"]::before { top: 12px; }
.docbtn[class*="align-"]::after  { top: 16px; }
.docbtn.align-left::after   { right: 13px; }
.docbtn.align-right::after  { left: 13px; }
.docbtn.align-center::after { left: 10px; right: 10px; }

/* A pressed toggle. The whole reason for aria-pressed is that somebody can
   tell where the caret is without retyping a word to find out, so this has
   to be obvious rather than tasteful. */
.docbtn[aria-pressed="true"] {
  background: var(--color-accent-100, rgba(200,102,58,.16));
  color: var(--color-accent-800, #c8663a);
  border-color: color-mix(in srgb, var(--color-accent, #c8663a) 34%, transparent);
}
body.glass .docbtn[aria-pressed="true"] {
  background: rgba(200,102,58,.26); color: #f0c3a8; border-color: rgba(200,102,58,.5);
}
.dicon {
  width: 17px; height: 17px; fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}

/* Colour lives behind one button. Twelve swatches in the rail is most of a
   toolbar's width spent on the control people reach for least often. */
.docpop { position: relative; }
.docbtn-pop { flex-direction: column; gap: 1px; padding: 0 8px; }
.dpop-a { font-size: 13px; font-weight: 700; line-height: 1; }
.dpop-bar {
  display: block; width: 15px; height: 3px; border-radius: 1px;
  background: #1a1a1a;
}
.docpanel {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 20;
  padding: 10px; border-radius: var(--radius-md);
  border: 1px solid var(--color-divider); background: var(--color-surface);
  box-shadow: 0 8px 28px rgba(20,18,16,.22);
}
body.glass .docpanel {
  background: var(--glass-strong); border-color: var(--hair);
  -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);
}
.docpanel[hidden] { display: none; }
.dpanel-t {
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--color-neutral-600); margin: 2px 0 5px;
}
.dpanel-t + .dswatches { margin-bottom: 8px; }
.dswatches { display: grid; grid-template-columns: repeat(7, 22px); gap: 5px; }
.dsw {
  width: 22px; height: 22px; padding: 0; cursor: pointer; border-radius: 5px;
  border: 1px solid rgba(0,0,0,.18); background: var(--sw);
}
.dsw:hover, .dsw:focus-visible { outline: 2px solid var(--color-accent, #c8663a); outline-offset: 1px; }
/* "No highlight" is the absence of one, drawn as an absence. */
.dsw-none {
  background: linear-gradient(135deg,
    transparent calc(50% - 1px), #b03636 50%, transparent calc(50% + 1px));
  background-color: #fff;
}

/* A sheet of paper, and it says so: white ground, a shadow, and a maximum
   width that has nothing to do with the window. A demand letter is going to
   be printed, and a line that runs the width of a monitor does not survive
   the trip. */
.docpaper {
  background: #fff; color: #1a1a1a;
  border: 1px solid var(--color-divider); border-top: 0;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
  min-height: 460px; overflow-wrap: break-word;
}
.docpage { outline: none; min-height: 380px; }
.docpage p, .docpage h1, .docpage h2, .docpage ul { margin: 0 0 .55em; }
.docpage h1 { font-size: 1.6em; }
.docpage h2 { font-size: 1.25em; }
.docpage ul { padding-left: 1.4em; }
/* An empty document is a white rectangle, which looks like a page that
   failed to load. The flag is set in JS because "empty" here means a single
   paragraph holding a <br>, which no CSS selector can ask about. */
.docpage[data-empty="1"]::before {
  content: 'Start writing.'; color: #a8a29b; pointer-events: none;
}
/* Clicking the margin puts the caret in the text. A sheet of paper with a
   dead border is not a sheet of paper. */
.docpaper { cursor: text; }
.docstatus {
  display: flex; justify-content: flex-end; gap: var(--space-3);
  font-size: 12px; color: var(--color-neutral-600);
  padding: 6px 2px 0;
}
.docfoot { margin-top: var(--space-2); }

.taskdraft { display: inline-flex; align-items: center; gap: 5px; }
.taskdraft svg {
  width: 14px; height: 14px; fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}

@media (max-width: 900px) {
  /* The page controls drop below the character ones rather than being
     squeezed next to them; they are used once per document, not per word. */
  .docgroup-page { margin-left: 0; border-left: 0; padding-left: 0; width: 100%; }
  .docgroup-page::before { content: none; }
}
@media (max-width: 640px) {
  .docrow { grid-template-columns: 28px minmax(0, 1fr) 24px; }
  .dochead { align-items: stretch; }
  .docname { min-width: 0; }
}

/* ── documents ──────────────────────────────────────────────────────────
   A folder of drafts, and the page you write one on.

   The editor is the only screen in Donellus that has to look like paper. The
   rest of the software is panels over a photograph and is deliberately not
   trying to be a physical object; a letter is different, because the person
   writing it is picturing the thing that comes out of the printer, and a
   demand laid out in the app's own type at the app's own width tells them
   nothing about whether it will fit on a page. So the sheet is white, has
   real margins, and holds its width when the window is wider than it. */

.docmake { margin-bottom: var(--space-5); }
.docmake .filters { margin-bottom: 0; }

.doclist { display: flex; flex-direction: column; gap: 2px; }
.docrow {
  display: grid; grid-template-columns: 34px minmax(0, 1fr) 28px;
  align-items: center; gap: var(--space-3);
  width: 100%; text-align: left; cursor: pointer;
  padding: 11px var(--space-3); border: 0; border-radius: var(--radius-md);
  background: transparent; font: inherit; color: inherit;
}
.docrow:hover { background: var(--color-neutral-100); }
.docrow:focus-visible { outline: 2px solid #c8663a; outline-offset: -2px; }
.docrow-ico {
  display: grid; place-items: center; width: 30px; height: 30px;
  border-radius: 8px; background: var(--color-accent-100); color: var(--color-accent-800);
}
.docrow-ico svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.6; }
.docrow-main { min-width: 0; display: block; }
.docrow-title {
  display: block; font-size: 14.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.docrow-sub {
  display: block; font-size: 12.5px; color: var(--color-neutral-600);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Only on hover. A row of discard crosses down the side of a folder reads as
   a list of things to get rid of, which is the opposite of what it is. */
.docrow-drop {
  display: grid; place-items: center; width: 24px; height: 24px;
  border-radius: 999px; font-size: 15px; line-height: 1;
  color: var(--color-neutral-600); opacity: 0;
}
.docrow:hover .docrow-drop, .docrow-drop:focus-visible { opacity: 1; }
.docrow-drop:hover { background: rgba(176,54,54,.12); color: #b03636; }

/* ── the editor's head ── */
.dochead {
  display: flex; align-items: center; gap: var(--space-3);
  flex-wrap: wrap; margin-bottom: 4px;
}
/* The title is an input that does not look like one until you are in it: on
   this screen it is the document's name, and a form field around the name of
   the thing you are reading is noise. */
.docname {
  flex: 1; min-width: 220px; font-size: 22px; font-weight: 600;
  border-color: transparent; background: transparent; padding-left: 6px;
}
.docname:hover { border-color: var(--color-divider); }
.docname:focus { border-color: var(--color-accent); background: var(--color-surface); }
.dochead-acts { display: flex; align-items: center; gap: var(--space-3); }
.docsaved { font-size: 12.5px; color: var(--color-neutral-600); white-space: nowrap; }
.docmeta {
  font-size: 13px; color: var(--color-neutral-600);
  margin: 0 0 var(--space-4) 6px;
}

/* ── the toolbar ── */
.doctools {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding: 8px; margin-bottom: var(--space-3);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  background: var(--color-surface);
  /* Follows the page down. A demand runs to four screens and nobody wants to
     scroll back to the top to make a word bold. */
  position: sticky; top: 8px; z-index: 5;
}
.docgroup { display: flex; align-items: center; gap: 4px; }
/* A hairline between groups instead of a gap wide enough to see. Ten controls
   in a row is a lot to take in; the rules say which ones belong together. */
.docgroup + .docgroup {
  padding-left: 8px; margin-left: 2px;
  border-left: 1px solid var(--color-divider);
}
/* The page controls go last and give way first — margins and spacing are set
   once at the start of a letter, not while writing it. */
.docgroup-page { margin-left: auto; }
.doclab { font-size: 12px; color: var(--color-neutral-600); }
.docsel { width: auto; min-width: 76px; padding: 5px 28px 5px 9px; font-size: 13px; }
.docbtn {
  display: grid; place-items: center; min-width: 30px; height: 30px;
  padding: 0 7px; cursor: pointer; font: inherit; font-size: 14px;
  border: 1px solid transparent; border-radius: 7px;
  background: transparent; color: var(--color-neutral-800);
}
.docbtn:hover { background: var(--color-neutral-100); }
.docbtn:focus-visible { outline: 2px solid #c8663a; outline-offset: 1px; }
.docbtn.on { background: var(--color-accent-100); color: var(--color-accent-800); }
/* The four alignment buttons are one glyph with different rules inside it,
   drawn here rather than with four characters that do not exist as a set. */
.docbtn[class*="align-"] { font-size: 0; position: relative; }
.docbtn[class*="align-"]::before,
.docbtn[class*="align-"]::after {
  content: ""; position: absolute; left: 7px; right: 7px; height: 1.5px;
  background: currentColor; border-radius: 1px;
}
.docbtn[class*="align-"]::before { top: 10px; }
.docbtn[class*="align-"]::after  { top: 15px; }
.docbtn.align-left::after   { right: 13px; }
.docbtn.align-right::after  { left: 13px; }
.docbtn.align-center::after { left: 10px; right: 10px; }

.docswatches { display: flex; gap: 3px; }
.docswatch {
  width: 19px; height: 19px; padding: 0; cursor: pointer;
  border: 1px solid rgba(0,0,0,.18); border-radius: 999px;
  background: var(--sw);
}
.docswatch:hover { transform: scale(1.14); }
.docswatch:focus-visible { outline: 2px solid #c8663a; outline-offset: 2px; }

/* ── the sheet ── */
.docpaper {
  max-width: 8.5in; margin: 0 auto var(--space-4);
  background: #fff; color: #1a1a1a;
  border: 1px solid var(--color-divider); border-radius: 3px;
  box-shadow: 0 1px 3px rgba(0,0,0,.07), 0 12px 34px rgba(0,0,0,.09);
  /* Inline: the margin, face, size and leading are the document's own and are
     written on to this element by renderDoc from values the server checked. */
}
.docpage { outline: none; min-height: 9in; }
.docpage:focus { outline: none; }
.docpage p, .docpage h1, .docpage h2, .docpage li { margin: 0 0 .6em; }
/* Headings scale from the page's own size rather than from a fixed pixel
   value, so raising the document to 14pt raises its headings with it. */
.docpage h1 { font-size: 1.9em; font-weight: 600; line-height: 1.2; margin-bottom: .5em; }
.docpage h2 { font-size: 1.35em; font-weight: 600; line-height: 1.25; }
.docpage ul { margin: 0 0 .6em; padding-left: 1.4em; }
.docpage li { margin-bottom: .25em; }
/* The empty page says what it is for. ::before rather than a placeholder
   element, so there is nothing inside the editable region for the caret to
   land in front of or for docFromDom to read back as a first paragraph. */
.docpage:empty::before,
.docpage p:only-child:empty::before {
  content: "Start writing."; color: var(--color-neutral-500); pointer-events: none;
}
.docfoot { margin-top: 0; }

@media (max-width: 900px) {
  /* Stacked, and the page controls rejoin the flow — at this width there is
     no room to hold them out to the right and they were only there to be out
     of the way of the ones used constantly. */
  .docgroup-page { margin-left: 0; }
  .doctools { position: static; }
  .docpaper { max-width: none; }
}

/* ── printing a letter ──────────────────────────────────────────────────
   A demand letter leaves this building on paper or as a PDF, and the
   browser's own print dialog does both. What it must not print is the
   application: no rail, no toolbar, no room behind the page. Only the
   sheet, at its real margins, in black on white. */
@media print {
  body, body.glass {
    background: #fff !important;
    color: #000 !important;
  }
  body.glass::before, body.glass::after,
  .skylight, .wall, .leafwind, .demo-ribbon,
  .sidebar, .backbar, .dochead, .docmeta, .doctools,
  .docstatus, .docfoot, .dial, .bighelp, .ws-hits {
    display: none !important;
  }
  .shell, .main, .content { display: block !important; padding: 0 !important; margin: 0 !important; }
  .docpaper {
    border: 0 !important; box-shadow: none !important; border-radius: 0 !important;
    background: #fff !important; color: #000 !important;
    min-height: 0 !important; max-width: none !important;
  }
  /* The page margins are the document's, set on .docpaper as padding — so
     the printer is told to add none of its own on top. */
  @page { margin: 0; }
  .docpage { min-height: 0 !important; }
  /* A heading alone at the foot of a page reads as a mistake in a letter. */
  .docpage h1, .docpage h2 { break-after: avoid-page; }
  .docpage p, .docpage li { orphans: 2; widows: 2; }
}

/* ── two people in one letter ───────────────────────────────────────────
   Presence, and the one collision the merge cannot resolve made visible
   before it happens rather than reported after. */
.docpeople { display: inline-flex; align-items: center; gap: 4px; }
.docwho {
  display: grid; place-items: center; width: 24px; height: 24px;
  border-radius: 999px; font-size: 10.5px; font-weight: 700; letter-spacing: .02em;
  color: #fff; background: var(--who); border: 2px solid var(--color-surface);
  margin-left: -6px;
}
.docwho:first-child { margin-left: 0; }
.docwho.me { box-shadow: 0 0 0 1.5px var(--who); }
body.glass .docwho { border-color: rgba(40,37,34,.9); }
.docwho-n { font-size: 12px; color: var(--color-neutral-600); margin-left: 6px; }

/* A ring round the paragraph somebody else is standing in. Deliberately on
   the paragraph and not the character: the paragraph is the unit that can
   collide, so the paragraph is what gets marked. */
.doc-busy {
  box-shadow: inset 2px 0 0 var(--busy);
  padding-left: 8px; margin-left: -10px;
  border-radius: 2px;
  transition: box-shadow .18s ease;
}
.doclive-note {
  margin-right: auto; color: var(--color-accent-800, #c8663a); font-size: 12px;
}
.doclive-note[hidden] { display: none; }
.docstatus { justify-content: space-between; }

/* ── uploaded files ─────────────────────────────────────────────────────
   The file input itself is never shown — it cannot be styled to match
   anything, and every browser draws it differently. The <label> is the
   button, which is a real one for the keyboard as well: a label pointing
   at an input is focusable through it and activates on Enter. */
.uploader { display: inline-flex; }
.upl-input {
  /* Not display:none — a hidden input is skipped by the accessibility tree
     and cannot be focused, which takes the control away from anybody not
     using a mouse. One pixel, clipped, still focusable. */
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%);
  white-space: nowrap; border: 0;
}
.upl-btn { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.upl-btn svg {
  width: 15px; height: 15px; fill: none;
  stroke: currentColor; stroke-width: 1.7; stroke-linecap: round;
}
.upl-input:focus-visible + .upl-btn { outline: 2px solid #c8663a; outline-offset: 2px; }
.upl-btn.is-off { opacity: .5; cursor: not-allowed; }

.filelist { display: flex; flex-direction: column; gap: 2px; margin-bottom: var(--space-5); }
.filerow {
  display: grid; grid-template-columns: 30px minmax(0, 1fr) auto 26px;
  align-items: center; gap: var(--space-3);
  padding: 9px var(--space-3); border-radius: var(--radius-md);
}
.filerow:hover { background: var(--color-neutral-100); }
.filerow-ico {
  display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 7px; background: var(--color-neutral-100); color: var(--color-neutral-600);
}
.filerow-ico svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.6; }
.filerow-main { min-width: 0; }
.filerow-name {
  display: block; font-size: 14px; font-weight: 600; color: inherit;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.filerow-name:hover { text-decoration: underline; }
.filerow-sub {
  display: block; font-size: 12.5px; color: var(--color-neutral-600);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Two states and they do not look alike. "Scanned" is quiet because it is
   the ordinary case; "not scanned" carries a warning colour because it is
   a fact somebody needs to weigh before opening the thing. Neither is ever
   shown for the other — see 014_client_files.sql. */
.filetag {
  font-size: 11px; font-weight: 600; letter-spacing: .03em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px; white-space: nowrap;
}
.filetag-ok { background: rgba(47,107,70,.13); color: #2f6b46; }
.filetag-warn { background: rgba(176,54,54,.13); color: #b03636; }
.filerow-drop {
  width: 24px; height: 24px; border: 0; border-radius: 999px; cursor: pointer;
  background: transparent; color: var(--color-neutral-600); font-size: 15px; line-height: 1;
  opacity: 0;
}
.filerow:hover .filerow-drop, .filerow-drop:focus-visible { opacity: 1; }
.filerow-drop:hover { background: rgba(176,54,54,.12); color: #b03636; }

body.glass .filerow:hover { background: rgba(255,255,255,.07); }
body.glass .filerow-ico { background: rgba(255,255,255,.09); color: rgba(244,243,240,.75); }
body.glass .filetag-ok { background: rgba(143,208,170,.16); color: #8fd0aa; }
body.glass .filetag-warn { background: rgba(240,150,150,.16); color: #f09696; }

@media (max-width: 640px) {
  /* The tag and the delete button drop to a second line rather than
     squeezing the name into nothing. */
  .filerow { grid-template-columns: 30px minmax(0, 1fr) 26px; }
  .filetag { grid-column: 2 / 4; justify-self: start; }
}

/* ── the private side of a case ─────────────────────────────────────────
   Shut by default and visibly a different kind of thing from the rest of
   the page: a lock, a cooler border, and no colour borrowed from the
   accent. It should not look like somewhere you wander into. */
.privpanel { margin-bottom: var(--space-6); padding: 0; overflow: hidden; }
.priv-head {
  display: flex; align-items: center; gap: var(--space-3);
  width: 100%; padding: 13px var(--space-4); cursor: pointer;
  border: 0; background: transparent; font: inherit; color: inherit; text-align: left;
}
.priv-head:hover { background: var(--color-neutral-100); }
.priv-lock { display: grid; place-items: center; width: 26px; height: 26px; flex-shrink: 0; }
.priv-lock svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; }
.priv-title { font-weight: 600; font-size: 14.5px; }
.priv-sub { flex: 1; font-size: 12.5px; color: var(--color-neutral-600); }
.priv-chev { color: var(--color-neutral-600); font-size: 17px; }
.priv-head.open { border-bottom: 1px solid var(--color-divider); }
.privpanel.shut .priv-head { padding: 11px var(--space-4); }

.priv-warn {
  margin: var(--space-4) var(--space-4) 0; padding: 9px 12px; border-radius: var(--radius-md);
  background: rgba(176,54,54,.1); color: #b03636; font-size: 13px;
}
.privgrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-5); padding: var(--space-4);
}
.privcell { min-width: 0; }
.priv-k {
  display: block; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--color-neutral-600); margin-bottom: 5px;
}
.priv-v { display: flex; align-items: center; gap: 8px; font-size: 14px; margin-bottom: 7px; flex-wrap: wrap; }
/* Tabular figures so a column of masked numbers lines up and a changed
   digit is visible rather than merely different. */
.priv-mask, .priv-clear { font-variant-numeric: tabular-nums; letter-spacing: .04em; }
.priv-clear {
  padding: 2px 8px; border-radius: 6px; font-size: 13.5px;
  background: rgba(200,102,58,.12); color: #c8663a;
}
.priv-none { color: var(--color-neutral-500); font-size: 13.5px; }
.priv-reveal {
  border: 1px solid var(--color-divider); background: transparent; cursor: pointer;
  border-radius: 6px; padding: 2px 9px; font: inherit; font-size: 12px;
  color: var(--color-neutral-700);
}
.priv-reveal:hover { background: var(--color-neutral-100); }
.priv-edit, .priv-pay, .setrow {
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 9px;
}
.priv-edit .input, .priv-pay .input, .setrow .input { flex: 1; min-width: 110px; }
.input-sm { padding: 6px 10px; font-size: 13.5px; }
.input.narrow, .setrow .input.narrow { flex: 0 0 90px; min-width: 90px; }
.priv-h { margin: var(--space-4) var(--space-4) 8px; font-size: 15px; }
.setrow { padding: 0 var(--space-4); }

.settle { width: 100%; border-collapse: collapse; margin: 0 0 var(--space-3); font-size: 13.5px; }
.settle td { padding: 7px var(--space-4); border-bottom: 1px solid var(--color-divider); }
.sl-kind { text-transform: capitalize; color: var(--color-neutral-600); width: 90px; }
/* Amounts right-aligned and tabular: a settlement statement is read down
   the column, and proportional digits make that impossible. */
.sl-amt { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.sl-empty { color: var(--color-neutral-500); }
.settle tfoot td { border-bottom: 0; padding-top: 5px; padding-bottom: 5px; }
.settle tfoot tr:first-child td { border-top: 1px solid var(--color-divider); padding-top: 9px; }
.sl-net td { font-weight: 700; font-size: 15px; border-top: 1px solid var(--color-divider); }
.sl-drop {
  border: 0; background: transparent; cursor: pointer; color: var(--color-neutral-600);
  border-radius: 999px; width: 22px; height: 22px; font-size: 14px; line-height: 1;
}
.sl-drop:hover { background: rgba(176,54,54,.12); color: #b03636; }
.priv-why { margin: 0 var(--space-4) var(--space-4); font-size: 12.5px; }

body.glass .priv-head:hover { background: rgba(255,255,255,.07); }
body.glass .priv-clear { background: rgba(240,180,140,.16); color: #f0b48c; }
body.glass .settle td { border-color: rgba(255,255,255,.12); }

@media (max-width: 560px) {
  .privgrid { grid-template-columns: 1fr; }
  /* The summary drops onto its own line under the title. It was already
     asking for width:100% and order:3, but .priv-head does not wrap, so
     instead of moving down it simply ran off the right of the card. Nothing
     scrolled sideways — the page widened and the browser zoomed out — so it
     read as slightly small rather than as broken. */
  .priv-head { flex-wrap: wrap; }
  .priv-sub { flex: none; width: 100%; order: 3; }
}
/* The category sits against the upload button as one control. */
.upl-cat { width: auto; min-width: 130px; margin-right: 6px; }
@media (max-width: 560px) { .upl-cat { min-width: 0; max-width: 130px; } }
/* What is still missing before this person can actually be paid. Amber
   rather than red: it is an unfinished file, not an error. */
.priv-gap {
  margin: 0 0 9px; padding: 7px 10px; border-radius: var(--radius-md);
  background: rgba(180,130,40,.13); color: #8a6d1f; font-size: 12.5px;
}
body.glass .priv-gap { background: rgba(230,190,110,.15); color: #e6be6e; }
/* PERF[LAZY]: the foot of a paged list. Says what is shown out of what, so
   a capped list never looks like a complete one. */
.morerow {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-4) 0 var(--space-2);
}
.morerow .muted { font-size: 13px; }
/* Earlier messages sit above the thread, not below it: a conversation is
   read from the bottom and "earlier" goes upward. */
.chat-more { justify-content: center; padding-top: 0; }

/* ── two-step sign-in ───────────────────────────────────────────────────
   The setup key is read off the screen and typed into a phone, so it is
   monospaced, spaced in fours, and large enough to read at arm's length. */
.mfakey {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  margin: var(--space-3) 0;
  padding: 12px var(--space-4); border-radius: var(--radius-md);
  background: var(--color-neutral-100);
}
.mfakey code {
  font-size: 16px; letter-spacing: .08em; font-variant-numeric: tabular-nums;
  word-break: break-all;
}
/* Recovery codes are printed or pasted into a password manager, so they are
   laid out to be read in a column rather than a paragraph. */
.reccodes {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 6px; margin: var(--space-3) 0;
}
.reccodes code {
  padding: 7px 10px; border-radius: 7px; text-align: center;
  background: var(--color-neutral-100); font-size: 14px; letter-spacing: .06em;
}
/* The code field on the sign-in screen: one job, six characters, centred so
   the caret is where the eye already is. */
.code-input {
  text-align: center; font-size: 22px; letter-spacing: .3em;
  font-variant-numeric: tabular-nums;
}
.code-input::placeholder { letter-spacing: .3em; opacity: .35; }
body.glass .mfakey, body.glass .reccodes code { background: rgba(255,255,255,.08); }

/* ── the enrolment gate ──────────────────────────────────────────────────
   Somebody signed in who cannot go anywhere until they have set up a second
   factor. It sits on the sign-in screen's ground, so arriving here from the
   password form reads as the same place asking for one more thing.

   It borrows .login-card's colours deliberately rather than inventing a
   card: this screen appears before the app, where the glass theme's light
   text on dark panels does not apply, and a second set of card styles is a
   second thing to keep in step. Wider than .login-card because it carries a
   secret key to copy and then ten recovery codes, neither of which reads
   well in a column sized for two fields. */
.enrolgate {
  width: min(560px, 100%);
  padding: var(--space-6);
  border-radius: calc(var(--radius-lg) * 1.15);
  background: #ffffff;
  border: 1px solid #ececef;
  box-shadow: 0 6px 24px rgba(28,30,33,.08);
  color: var(--color-neutral-900);
}
.enrolgate h2 {
  font-family: var(--font-display);
  color: var(--brand);
  margin: 0 0 var(--space-2);
  line-height: 1.1;
}
.enrolgate > .why {
  font-size: 14px; color: var(--color-neutral-700);
  margin: 0 0 var(--space-5); max-width: 52ch;
}

/* The block inside already carries a frame in Settings and does not need a
   second one here. The `body.glass` selector is repeated because the theme's
   panel rule is more specific than a bare `.enrolgate .setblock`, and
   without it the gate paints a dark translucent panel onto a white card. */
.enrolgate .setblock,
body.glass .enrolgate .setblock {
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border: 0;
  box-shadow: none;
  padding: 0;
  margin: 0 0 var(--space-4);
  max-width: none;
}
.enrolgate .setblock h3 { color: var(--color-neutral-900); }
.enrolgate .setblock .why,
.enrolgate .mfakey,
.enrolgate .mfacodes { color: var(--color-neutral-700); }
.enrolgate .setrow { margin-top: var(--space-2); }

/* The glass theme paints headings and buttons for dark panels, and it does
   so at a specificity (`body.glass h2` is 0-1-2) that a bare `.enrolgate h2`
   cannot reach. The gate is a white card on the sign-in ground rather than
   part of the app's chrome, so it opts back out — matched selector for
   matched selector, rather than with !important. */
body.glass .enrolgate h2 { color: var(--brand); }
body.glass .enrolgate .setblock h3 { color: var(--color-neutral-900); }
body.glass .enrolgate .setblock .why,
body.glass .enrolgate > .why { color: var(--color-neutral-700); }

body.glass .enrolgate .btn-secondary {
  color: var(--color-neutral-900);
  border-color: #d8d9dd;
  background: #fff;
}
body.glass .enrolgate .btn-secondary:hover:not(:disabled) { background: #f4f4f6; }
body.glass .enrolgate .btn-ghost { color: var(--color-neutral-700); }
body.glass .enrolgate .btn-ghost:hover { background: #f0f0f2; color: var(--color-neutral-900); }

/* ── the enrolment QR ────────────────────────────────────────────────────
   Sized so a phone held at arm's length resolves the modules comfortably.
   The white plate is part of the SVG rather than the container, so the
   quiet zone survives whatever the block behind it is coloured. */
.mfaqr {
  margin: 0 0 var(--space-4);
}
.mfaqr .qr {
  width: 190px; height: 190px; display: block;
  border-radius: var(--radius-md);
  box-shadow: 0 1px 3px rgba(28,30,33,.14);
  /* Geometry, not pixels — but say so, in case a browser decides to smooth
     it when the page is zoomed. */
  shape-rendering: crispEdges;
}

/* Signed in right now. Not a status like active/disabled — a fact about
   this moment — so it reads quieter than those and sits beside them. */
.tag-live {
  background: rgba(46,125,88,.14);
  color: #1f6b46;
  border: 1px solid rgba(46,125,88,.28);
}
body.glass .tag-live {
  background: rgba(120,220,170,.18);
  color: #bff0d6;
  border-color: rgba(120,220,170,.32);
}

/* ── the break-glass receipt ─────────────────────────────────────────────
   Somebody with the server key cleared an account's second factor. Above
   everything, and it does not fade: this is not a warning about something
   that might happen, it is a receipt for something that already did.

   Clay rather than red. Red is what the interface uses for a thing that has
   gone wrong, and this has not gone wrong — it was done deliberately, by
   somebody who had to type a reason, and the firm is being told. */
.bg-banner {
  display: flex; align-items: center; gap: var(--space-4);
  padding: 11px 18px;
  /* body.glass::before is a fixed, z-index 0 wash over the photograph, and a
     static block paints UNDERNEATH a positioned one at the same level — so
     without this the receipt is drawn and then washed out by the backdrop.
     .shell is position:relative for the same reason. */
  position: relative; z-index: 1;
  background: #f6e4d8; color: #6d3a20;
  border-bottom: 1px solid #e3c6b3;
  font-size: 13.5px; line-height: 1.45;
}
.bg-banner + .bg-banner { border-top: 1px solid #efd6c5; }
.bg-body { flex: 1; }
.bg-reason { display: block; font-style: italic; }
.bg-op { opacity: .75; font-size: 12.5px; }
.bg-banner .btn { flex: none; background: #fff; border-color: #dcbda9; color: #6d3a20; }
.bg-banner .btn:hover { background: #fdf6f1; }
/* The shell sits under it rather than beside it, on either ground. */
body.glass .bg-banner { background: #6d4630; color: #f7e6da; border-bottom-color: #7d5540; }
body.glass .bg-banner .btn { background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.24); color: #f7e6da; }

/* ── Case value ───────────────────────────────────────────────────────────
   An evaluation is read aloud to a client, so the figures have to be legible
   at arm's length and the caveat has to sit beside the number rather than
   under it in small type. */
.evaluate { padding: var(--space-5) var(--space-6) var(--space-8); max-width: 1080px; }

.ev-head { display: flex; align-items: flex-start; gap: var(--space-4); margin-bottom: var(--space-5); }
.ev-head h2 { margin: 0; font-size: 1.35rem; }
.ev-sub { margin: 2px 0 0; color: var(--color-neutral-600); font-size: .85rem; }
.ev-head .btn { margin-left: auto; }

.ev-grid { display: grid; gap: var(--space-4);
           grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.ev-card { background: var(--color-surface); border: 1px solid var(--color-neutral-200);
           border-radius: 14px; padding: var(--space-4); }
.ev-card h3 { margin: 0 0 4px; font-size: .95rem; }
.ev-why { margin: 0 0 var(--space-3); color: var(--color-neutral-600);
          font-size: .8rem; line-height: 1.45; }

.ev-field { display: block; margin-bottom: var(--space-3); }
.ev-label { display: block; font-size: .78rem; color: var(--color-neutral-600); margin-bottom: 4px; }
.ev-input { display: flex; align-items: center; border: 1px solid var(--color-neutral-300);
            border-radius: 9px; background: var(--color-neutral-100); overflow: hidden; }
.ev-input:focus-within { border-color: var(--color-accent, #c8663a); }
.ev-prefix { padding: 0 0 0 10px; color: var(--color-neutral-600); font-variant-numeric: tabular-nums; }
.ev-input input, .ev-notes textarea {
  width: 100%; border: 0; background: transparent; color: inherit;
  font: inherit; padding: 9px 10px; font-variant-numeric: tabular-nums;
}
.ev-input input:focus, .ev-notes textarea:focus { outline: none; }
.ev-hint { display: block; font-size: .72rem; color: var(--color-neutral-500); margin-top: 3px; }
.ev-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }

.ev-out { margin-top: var(--space-5); background: var(--color-surface);
          border: 1px solid var(--color-neutral-200); border-radius: 14px; padding: var(--space-4); }
.ev-out h3 { margin: 0 0 var(--space-3); font-size: .95rem; }
.ev-missing { margin: 0 0 var(--space-3); font-size: .8rem; color: var(--color-neutral-600); }

.ev-row { display: flex; align-items: baseline; gap: var(--space-3);
          padding: 9px 0; border-top: 1px solid var(--color-neutral-200); }
.ev-row:first-of-type { border-top: 0; }
.ev-row-label { color: var(--color-neutral-700); font-size: .875rem; }
.ev-row-value { margin-left: auto; font-variant-numeric: tabular-nums; font-size: .95rem; }
.ev-dash { color: var(--color-neutral-500); padding: 0 2px; }
/* The one figure a client remembers. */
.ev-net { border-top-width: 2px; }
.ev-net .ev-row-label { font-weight: 600; color: var(--color-neutral-900); }
.ev-net .ev-row-value { font-size: 1.15rem; font-weight: 600; }

.ev-capped { margin: var(--space-3) 0 0; padding: 10px 12px; border-radius: 9px;
             background: var(--color-neutral-100); border: 1px solid var(--color-neutral-300);
             font-size: .82rem; line-height: 1.45; color: var(--color-neutral-700); }

/* Beside the number, not beneath it. A range with no caveat next to it reads
   as a market answer, and this one is not. */
.ev-nodata { margin-top: var(--space-4); padding: 12px 14px; border-radius: 10px;
             border: 1px solid var(--color-neutral-300); background: var(--color-neutral-100);
             font-size: .82rem; line-height: 1.5; color: var(--color-neutral-700); }
.ev-nodata strong { color: var(--color-neutral-900); }
.ev-doc { font-family: ui-monospace, Menlo, monospace; font-size: .95em; }

.ev-actions { margin-top: var(--space-4); display: flex; justify-content: flex-end; }
.ev-notes { display: block; margin-top: var(--space-4); }
.ev-notes textarea { border: 1px solid var(--color-neutral-300); border-radius: 9px;
                     background: var(--color-neutral-100); resize: vertical; }

/* ── the crash facts, the two methods, and the reader ──────────────────
   Added when the screen stopped being a calculator and started being the
   place a firm records what happened. Three new shapes: dropdowns for the
   constrained vocabularies, a two-column comparison for pain and suffering,
   and the proposal cards from a read demand package. */

/* A panel that has more to say than fits in a column of the grid. */
.ev-wide { grid-column: 1 / -1; }

.ev-selects { display: grid; gap: var(--space-3);
              grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.ev-input select { width: 100%; border: 0; background: transparent; color: inherit;
                   font: inherit; padding: 9px 10px; appearance: none;
                   background-image: linear-gradient(45deg, transparent 50%, var(--color-neutral-500) 50%),
                                     linear-gradient(135deg, var(--color-neutral-500) 50%, transparent 50%);
                   background-position: calc(100% - 16px) 52%, calc(100% - 11px) 52%;
                   background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
                   padding-right: 30px; }
.ev-input select:focus { outline: none; }

.ev-checks { display: grid; gap: 6px; margin-top: var(--space-3);
             grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.ev-check { display: flex; align-items: flex-start; gap: 9px; padding: 7px 9px;
            border: 1px solid var(--color-neutral-200); border-radius: 9px;
            background: var(--color-neutral-100); cursor: pointer; }
.ev-check:hover { border-color: var(--color-neutral-300); }
.ev-check input { margin-top: 2px; flex: none; accent-color: var(--color-accent, #c8663a); }
.ev-check-label { font-size: .82rem; line-height: 1.35; }
.ev-check-hint { display: block; font-size: .72rem; color: var(--color-neutral-500); margin-top: 2px; }

/* Two methods side by side, and never averaged into a third. */
.ev-methods { display: grid; gap: var(--space-4);
              grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.ev-method { border: 1px solid var(--color-neutral-200); border-radius: 12px;
             padding: var(--space-3); background: var(--color-neutral-100); }
.ev-method h4 { margin: 0 0 6px; font-size: .82rem; color: var(--color-neutral-600);
                text-transform: uppercase; letter-spacing: .04em; }
.ev-method-sum { margin: 0; font-size: 1.25rem; font-weight: 600;
                 font-variant-numeric: tabular-nums; }
.ev-method-none { color: var(--color-neutral-500); font-weight: 500; }
.ev-method-how { margin: 4px 0 0; font-size: .8rem; color: var(--color-neutral-700); }
.ev-method-note { margin: 8px 0 0; font-size: .75rem; line-height: 1.45;
                  color: var(--color-neutral-600); }
.ev-method-warn { margin: 8px 0 0; font-size: .75rem; line-height: 1.45;
                  color: var(--color-neutral-800); padding: 8px 10px; border-radius: 8px;
                  background: var(--color-surface); border: 1px solid var(--color-neutral-300); }
.ev-method .ev-field { margin-top: var(--space-3); margin-bottom: 0; }
.ev-method .ev-input { background: var(--color-surface); }

/* Where the suggestion came from. The reasons are the reason to trust it. */
.ev-suggest { margin: var(--space-3) 0 0; padding: 10px 12px; border-radius: 9px;
              background: var(--color-surface); border: 1px solid var(--color-neutral-200);
              font-size: .78rem; line-height: 1.45; color: var(--color-neutral-700); }
.ev-suggest ul { margin: 5px 0 0; padding-left: 18px; }
.ev-suggest li { margin: 2px 0; }
.ev-overridden { border-color: var(--color-accent, #c8663a); }

.ev-agreement { margin: var(--space-3) 0 0; padding: 10px 12px; border-radius: 9px;
                font-size: .8rem; line-height: 1.45;
                background: var(--color-neutral-100);
                border: 1px solid var(--color-neutral-300); color: var(--color-neutral-700); }
.ev-agree-yes { border-left: 3px solid #3f8a5c; }
.ev-agree-no  { border-left: 3px solid #c8663a; }

/* Deadlines and coverage traps. Louder than a hint, quieter than an error —
   these are things that lose a case, not things that stop a calculation. */
.ev-warnings { margin: 0 0 var(--space-3); padding: 10px 12px; border-radius: 9px;
               background: var(--color-neutral-100); border: 1px solid var(--color-neutral-300);
               border-left: 3px solid #c8663a; }
.ev-warnings p { margin: 0 0 7px; font-size: .8rem; line-height: 1.45;
                 color: var(--color-neutral-800); }
.ev-warnings p:last-child { margin-bottom: 0; }
.ev-check-high { font-weight: 600; }

.ev-excluded { margin-top: var(--space-4); font-size: .78rem; color: var(--color-neutral-600); }
.ev-excluded summary { cursor: pointer; }
.ev-excluded ul { margin: 8px 0 0; padding-left: 18px; line-height: 1.5; }

/* ── proposals from a read document ─────────────────────────────────── */
.ev-reader textarea { width: 100%; border: 1px solid var(--color-neutral-300);
                      border-radius: 9px; background: var(--color-neutral-100);
                      color: inherit; font: inherit; font-size: .85rem;
                      padding: 10px; resize: vertical; }
.ev-reader textarea:focus { outline: none; border-color: var(--color-accent, #c8663a); }
.ev-reader .ev-actions { justify-content: flex-start; }

.ev-founds { display: grid; gap: var(--space-3); margin-top: var(--space-3);
             grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.ev-found { border: 1px solid var(--color-neutral-200); border-radius: 11px;
            padding: var(--space-3); background: var(--color-neutral-100); }
.ev-found-head { display: flex; align-items: center; gap: 8px; }
.ev-found-label { font-size: .75rem; color: var(--color-neutral-600); }
.ev-found-conf { margin-left: auto; font-size: .65rem; text-transform: uppercase;
                 letter-spacing: .05em; padding: 2px 6px; border-radius: 5px;
                 background: var(--color-neutral-200); color: var(--color-neutral-700); }
.ev-conf-high { background: #dcefe2; color: #24603d; }
.ev-conf-low  { background: #f3e2d8; color: #8a4526; }
.ev-found-value { margin-top: 3px; font-size: 1rem; font-weight: 600;
                  font-variant-numeric: tabular-nums; }
/* The sentence it was read from. Without this the proposal is just an
   assertion, and checking it means re-reading eleven pages. */
.ev-found-quote { margin: 8px 0 0; padding: 7px 10px; border-left: 2px solid var(--color-neutral-300);
                  font-size: .74rem; line-height: 1.45; color: var(--color-neutral-600); }
.ev-found-quote cite { display: block; margin-top: 3px; font-style: normal;
                       font-size: .68rem; color: var(--color-neutral-500); }
.ev-found .btn { margin-top: 9px; }
.btn-sm { padding: 5px 10px; font-size: .78rem; }

/* An assigned task starts when you open it. The affordance has to be there
   before the click, or the move looks like the app did something on its own. */
.taskrow-main.startable { cursor: pointer; border-radius: 8px; margin: -4px -6px;
                          padding: 4px 6px; transition: background .12s ease; }
.taskrow-main.startable:hover,
.taskrow-main.startable:focus-visible { background: rgba(244, 81, 30, .10); }
.taskrow-main.startable:focus-visible { outline: 2px solid rgba(244,81,30,.6); outline-offset: 1px; }
.taskrow-start { margin-left: 9px; font-size: .66rem; text-transform: uppercase;
                 letter-spacing: .06em; color: var(--color-neutral-500);
                 opacity: 0; transition: opacity .12s ease; }
.taskrow-main.startable:hover .taskrow-start,
.taskrow-main.startable:focus-visible .taskrow-start { opacity: 1; }

/* ── the client's page, from the firm's side ───────────────────────────
   Set apart from the private panel it sits beside, because the two are
   opposites: that one is things nobody outside may see, this one is the
   only things they may. */
.portalpanel { border-left: 3px solid #2f6b46; }

/* ── the demand interview ───────────────────────────────────────────────
   One question, and the sentence it lands in. The letter is the larger
   half on purpose: the question is a prompt, the sentence is the work. */
/* Both panels float off the search bar. Nothing here takes part in the
   flow, because anything that does pushes the bar down the page, and the
   bar is the one thing on this screen that must stay put.

   The letter sits exactly where .peek sits and at the same width, so the
   two previews are one object in one place rather than two shapes.

   The index sits to the LEFT of the bar, clear of it. `right: 100%` pins it
   to the bar's left edge; the bar is centred, so on a wide screen there is
   room, and below 1100px it moves above the letter instead of overlapping
   the page. */
.ivpane { position: absolute; left: 0; right: 0; bottom: 100%; z-index: 30;
          margin-bottom: 10px;
          padding: var(--space-4); border-radius: 14px;
          background: rgba(255,255,255,.55); border: 1px solid var(--color-neutral-200);
          -webkit-backdrop-filter: blur(22px) saturate(1.2);
                  backdrop-filter: blur(22px) saturate(1.2);
          box-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 8px 28px rgba(28,30,33,.12);
          max-width: 720px; text-align: left;
          /* Same ceiling as the case file, set in pixels by minis.js. This
             card is usually short enough not to need it; a long excerpt on
             a laptop screen is not. */
          overflow-y: auto; }
/* ── windows carried aside ──────────────────────────────────────────────
   The demand interview and the case file can be picked up by their header
   and parked at either side of the room, in the same two bands the cork
   boards use. Parked, they are a chip: a name and a count, and nothing
   else. A minimised window that is still a small window is a small window —
   too small to read and still covering something. */
#minidock { position: fixed; inset: 0; z-index: 40; pointer-events: none; }

/* And it arrives with everything else. The dock is appended to the BODY —
   it has to be, because it floats over every screen and must survive the
   application re-rendering — which also puts it outside `.shell`, so the
   arrival animation that fades the interface in was never reaching it.
   Windows somebody had carried aside snapped into existence over a screen
   that was still fading up.

   Same timing as `.wall` above, and for the same reason: these are two sets
   of things hanging in the same room, and one of them arriving early reads
   as a glitch rather than as a difference.

   And it leaves the same way. Signing out used to take the chips out on the
   click while everything around them was still fading — see leave() in
   public/minis.js, which freezes the dock so this can fade it instead. They
   are still REMOVED at the end of that fade rather than left transparent: a
   client's name at opacity zero is a client's name in the markup. */
body.ui-arriving #minidock { animation: uiArrive 4s ease-out 2s 1 both; }
body.ui-leaving  #minidock { animation: uiLeave  2s ease-in 1 both; }
.minichip {
  position: fixed; pointer-events: auto; cursor: grab;
  display: flex; align-items: center; gap: 8px;
  max-width: 15rem; padding: 8px 12px 8px 8px;
  font: inherit; text-align: left;
  border-radius: 11px; border: 1px solid rgba(255,255,255,.16);
  background: rgba(26,24,22,.88); color: #fdfdfc;
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
          backdrop-filter: blur(18px) saturate(1.2);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 10px 28px rgba(0,0,0,.38);
}
.minichip:hover { border-color: rgba(255,255,255,.3); }
.minichip:active { cursor: grabbing; }
.minichip-grip { flex: 0 0 auto; width: 13px; height: 15px; opacity: .5; }
.minichip-grip svg { width: 100%; height: 100%; fill: none; stroke: currentColor;
                     stroke-width: 3; stroke-linecap: round; }
.minichip-text { display: flex; flex-direction: column; min-width: 0; }
.minichip-text b { font-size: .8rem; font-weight: 600; white-space: nowrap;
                   overflow: hidden; text-overflow: ellipsis; }
.minichip-text span { font-size: .7rem; color: rgba(253,253,252,.6); }

/* ── a folder, made by dropping one chip on another ─────────────────────
   Same shape and weight as a chip, because it is one — it just stands for
   two windows instead of one. The icon is grey rather than coloured: this
   is a container, and a container that draws the eye harder than the things
   inside it has the emphasis backwards.

   It opens IN PLACE rather than putting a sheet beside itself. A sheet is
   dismissed by clicking away from it, and clicking away from it is exactly
   what somebody does when they open one of the windows inside — so taking
   the second one out meant finding and pressing the folder again. Opened
   here it stays opened, and stays opened while its windows are being worked
   on, until somebody shuts it. */
.minifolder {
  position: fixed; pointer-events: auto;
  max-width: 15rem;
  font: inherit; text-align: left;
  border-radius: 11px; border: 1px solid rgba(255,255,255,.16);
  background: rgba(26,24,22,.88); color: #fdfdfc;
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
          backdrop-filter: blur(18px) saturate(1.2);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 10px 28px rgba(0,0,0,.38);
}
.minifolder:hover { border-color: rgba(255,255,255,.3); }

/* The head is the folder-as-chip: the whole grab handle, and the thing that
   opens and shuts it. The body below is not draggable — a press inside it is
   aimed at a row. */
.minifolder-head {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px 8px 8px; cursor: grab;
}
.minifolder-head:active { cursor: grabbing; }
.minifolder-icon { flex: 0 0 auto; width: 20px; height: 20px; }
.minifolder-icon svg { width: 100%; height: 100%; fill: none;
                       stroke: rgba(200,200,200,.85); stroke-width: 1.6;
                       stroke-linejoin: round; }
.minifolder-chev {
  flex: 0 0 auto; width: 14px; height: 14px; fill: none;
  stroke: rgba(253,253,252,.5); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform .16s ease;
}
.minifolder.open .minifolder-chev { transform: rotate(180deg); }
/* The arrow still turns over — it is what says which way the folder is. It
   just arrives there rather than sweeping. */
@media (prefers-reduced-motion: reduce) {
  .minifolder-chev { transition: none; }
}

.minifolder-body {
  padding: 2px 8px 8px; display: grid; gap: 4px;
  border-top: 1px solid rgba(255,255,255,.10);
  margin-top: 2px;
}
/* Where the ones that are open have gone, said once rather than as a badge
   on every row. */
.minifolder-out {
  margin: 2px 2px 0; font-size: .68rem; line-height: 1.35;
  color: rgba(253,253,252,.5);
}

/* What a carried chip is about to be filed with. */
.mini-target {
  border-color: rgba(255,236,214,.75) !important;
  box-shadow: inset 0 0 0 1px rgba(255,236,214,.4),
              0 0 26px rgba(255,225,190,.28) !important;
}

/* ── the ask, and the folder's own sheet ────────────────────────────────
   In the dock beside the chips rather than as a dialog over the page: the
   question is about two things somebody is holding, and a modal for it
   would cover the screen they are working on. */
.miniask {
  position: fixed; pointer-events: auto; z-index: 62;
  width: 15rem; padding: 11px 12px;
  border-radius: 11px; border: 1px solid rgba(255,255,255,.16);
  background: rgba(26,24,22,.94); color: #fdfdfc;
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
          backdrop-filter: blur(18px) saturate(1.2);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 14px 36px rgba(0,0,0,.45);
}
.miniask-q { margin: 0 0 8px; font-size: .84rem; font-weight: 600; }
.miniask input {
  width: 100%; font: inherit; font-size: .78rem; padding: 5px 7px;
  border-radius: 7px; border: 1px solid rgba(255,255,255,.2);
  background: rgba(255,255,255,.08); color: inherit;
}
.miniask input:focus { outline: none; border-color: rgba(255,236,214,.6); }
.miniask-do { display: flex; gap: 6px; margin-top: 9px; }
.miniask-do button {
  flex: 1 1 auto; font: inherit; font-size: .74rem; cursor: pointer;
  padding: 5px 8px; border-radius: 7px;
  border: 1px solid rgba(255,255,255,.16);
  background: rgba(255,255,255,.08); color: rgba(253,253,252,.9);
}
.miniask-do button:first-child {
  background: rgba(255,236,214,.16); border-color: rgba(255,236,214,.34);
  color: #ffeed9;
}
.miniask-do button:hover { background: rgba(255,255,255,.16); }

.minisheet-row {
  display: block; width: 100%; text-align: left; cursor: pointer;
  font: inherit; padding: 6px 8px; margin-bottom: 3px;
  border: 0; border-radius: 8px;
  background: rgba(255,255,255,.06); color: inherit;
}
.minisheet-row:hover { background: rgba(255,255,255,.14); }
.minisheet-row b { display: block; font-size: .78rem; font-weight: 600; }
.minisheet-row span { font-size: .7rem; color: rgba(253,253,252,.6); }

/* While it is in the air. Lifted, slightly turned, and out of the way of
   the pointer so the zone underneath can be seen. */
.minicarry { z-index: 60 !important; opacity: .92; pointer-events: none;
             box-shadow: 0 22px 50px rgba(0,0,0,.45) !important; }
body.mini-dragging { cursor: grabbing; user-select: none; }

/* The landing strip: the card's own footprint over the search bar. Offered
   only to something that is already parked, because dropping an open card
   back where it already is does nothing and a lit target would say
   otherwise. */
.ws-zone.mini-strip { display: grid; place-items: center; z-index: 5; }
.ws-zone.mini-strip span {
  font-size: .78rem; letter-spacing: .01em; padding: 5px 12px;
  border-radius: 999px; background: rgba(20,19,17,.6); color: rgba(255,244,230,.9);
}

/* ── the whole file for one client ──────────────────────────────────────

   Same anchor as the interview's card and a wider one, because it is
   showing five columns rather than one sentence. It floats off the bar for
   the same reason everything else here does: anything that takes part in
   the flow pushes the search bar down the screen, and the bar is the one
   thing on this page that must not move. */
.cfpane { position: absolute; left: 50%; transform: translateX(-50%);
          bottom: 100%; z-index: 30; margin-bottom: 10px;
          width: min(96vw, 1040px);
          padding: var(--space-4); border-radius: 14px;
          background: rgba(255,255,255,.55); border: 1px solid var(--color-neutral-200);
          -webkit-backdrop-filter: blur(22px) saturate(1.2);
                  backdrop-filter: blur(22px) saturate(1.2);
          box-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 8px 28px rgba(28,30,33,.12);
          text-align: left;
          /* A column, so the head stays put and the columns take what is
             left. The ceiling is set in pixels by minis.js, which measures
             the actual gap between the header and the search bar — there is
             no CSS value for that distance, and a vh figure is right at one
             window size and wrong at every other.

             overflow: hidden, not auto. Scrolling the whole card would move
             the client's name and the phase headers off the top, which are
             the two things worth being able to see the whole time. What
             scrolls is inside each column. */
          display: flex; flex-direction: column; overflow: hidden; }

.cf-head { flex: 0 0 auto; display: flex; align-items: center;
           gap: var(--space-3); margin-bottom: var(--space-4); }
/* Initials rather than a photograph. The sketch this came from shows an
   avatar; the application has no picture of anybody and inventing a
   placeholder face for a real person is worse than saying their initials. */
.cf-face { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%;
           display: grid; place-items: center;
           background: var(--color-accent-600, #5b7f9e); color: #fff;
           font-size: .82rem; font-weight: 600; letter-spacing: .02em; }
.cf-who { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.cf-who b { font-size: 1.02rem; }
.cf-count { font-size: .76rem; color: var(--color-neutral-500); }
.cf-btn { flex: 0 0 auto; font: inherit; font-size: .78rem; cursor: pointer;
          padding: 5px 11px; border-radius: 8px;
          border: 1px solid var(--color-neutral-300);
          background: var(--color-neutral-100); color: var(--color-neutral-800); }
.cf-btn:hover { background: var(--color-neutral-200); }
.cf-x, .cf-min { width: 28px; padding: 0; display: grid; place-items: center; }
.cf-x svg, .cf-min svg { width: 14px; height: 14px; fill: none; stroke: currentColor;
                         stroke-width: 1.8; stroke-linecap: round; }
.cf-loading { color: var(--color-neutral-500); font-size: .85rem; margin: 0; }

/* Five columns as drawn, falling to three, then two, then one. `auto-fit`
   with a minimum rather than a media query: the card's width is set by the
   window AND by how much room the bar leaves it, and a breakpoint on the
   viewport gets the second of those wrong. */
.cf-cols { display: grid; gap: var(--space-3);
           grid-template-columns: repeat(auto-fit, minmax(178px, 1fr));
           /* min-height: 0 is what lets a grid child be SHORTER than its
              content. Without it the columns take their natural height, the
              card grows past the ceiling above, and nothing scrolls. */
           flex: 1 1 auto; min-height: 0; }

.cf-col { border-radius: 11px; border: 1px solid var(--color-neutral-200);
          background: var(--color-neutral-100); overflow: hidden;
          display: flex; flex-direction: column; min-height: 0; }

/* Done is green, outstanding is red — as asked for. The colour is a band on
   the header rather than a fill behind the items, because at the start of a
   case every phase is outstanding and five red panels reads as five things
   wrong rather than as a case that has just opened. A phase nobody has put
   anything in is neither. */
.cf-col.done  { border-color: rgba(47,107,70,.34); }
.cf-col.open  { border-color: rgba(176,54,54,.30); }
.cf-col.done .cf-phase { background: rgba(47,107,70,.12); color: #2f6b46; }
.cf-col.open .cf-phase { background: rgba(176,54,54,.10); color: #a13a3a; }
.cf-col.empty .cf-phase { color: var(--color-neutral-500); }

.cf-phase { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; width: 100%;
            font: inherit; font-size: .82rem; font-weight: 600; cursor: pointer;
            padding: 8px 10px; border: 0; background: var(--color-neutral-200);
            text-align: left; }
.cf-phase-name { flex: 1 1 auto; min-width: 0; }
.cf-tally { flex: 0 0 auto; font-variant-numeric: tabular-nums; font-weight: 600; }
.cf-chev { flex: 0 0 auto; width: 13px; height: 13px; fill: none;
           stroke: currentColor; stroke-width: 2; stroke-linecap: round;
           transition: transform .18s ease; }
.cf-col.shut .cf-chev { transform: rotate(-90deg); }

/* The part that moves. A phase with eleven things in it scrolls within its
   own column; the four beside it do not, and the type stays the size it
   was. */
.cf-items { list-style: none; margin: 0; padding: 6px; display: flex;
            flex-direction: column; gap: 2px;
            flex: 1 1 auto; min-height: 0; overflow-y: auto;
            scrollbar-width: thin; }
.cf-items::-webkit-scrollbar { width: 7px; }
.cf-items::-webkit-scrollbar-thumb { border-radius: 4px;
                                     background: rgba(128,128,128,.34); }
body.glass .cf-items::-webkit-scrollbar-thumb { background: rgba(255,255,255,.22); }
.cf-item { display: flex; align-items: center; gap: 5px; }

.cf-tick { flex: 0 0 auto; width: 19px; height: 19px; padding: 0; cursor: pointer;
           display: grid; place-items: center; border-radius: 5px;
           border: 1.5px solid var(--color-neutral-400); background: transparent; }
.cf-tick svg { width: 11px; height: 11px; fill: none; stroke: currentColor;
               stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round;
               opacity: 0; }
.cf-item.done .cf-tick { background: #2f6b46; border-color: #2f6b46; color: #fff; }
.cf-item.done .cf-tick svg { opacity: 1; }

/* The label is its own button and going somewhere is all it does. One
   control doing both would mean every trip to the file drawer also ticked
   the box, and a checklist that ticks itself when you go and look is a
   checklist that lies. */
.cf-go { flex: 1 1 auto; min-width: 0; font: inherit; font-size: .78rem;
         line-height: 1.35; text-align: left; cursor: pointer;
         padding: 3px 4px; border: 0; border-radius: 5px; background: transparent;
         color: var(--color-neutral-800); }
.cf-go:hover { background: var(--color-neutral-200); text-decoration: underline; }
.cf-item.done .cf-go { color: var(--color-neutral-500); }

.cf-drop { flex: 0 0 auto; width: 18px; padding: 0; cursor: pointer;
           border: 0; background: transparent; font-size: .9rem; line-height: 1;
           color: var(--color-neutral-400); opacity: 0; transition: opacity .12s ease; }
.cf-item:hover .cf-drop, .cf-drop:focus-visible { opacity: 1; }
.cf-drop:hover { color: #b03636; }

/* The row while it is asking. Two words and two answers, in the space the
   row already occupies, so nothing else moves. */
.cf-item.asking { gap: 4px; padding: 2px 0; }
.cf-ask { flex: 1 1 auto; min-width: 0; font-size: .74rem;
          color: var(--color-neutral-700); }
.cf-yes, .cf-no { flex: 0 0 auto; font: inherit; font-size: .72rem; cursor: pointer;
                  padding: 2px 7px; border-radius: 6px; border: 1px solid transparent; }
.cf-yes { background: rgba(176,54,54,.16); color: #b03636;
          border-color: rgba(176,54,54,.34); }
.cf-yes:hover { background: rgba(176,54,54,.26); }
.cf-no  { background: transparent; color: var(--color-neutral-600); }
.cf-no:hover { background: var(--color-neutral-200); }

.cf-add { margin-top: 2px; }
.cf-add button { font: inherit; font-size: .74rem; cursor: pointer;
                 padding: 3px 5px; border: 0; border-radius: 5px;
                 background: transparent; color: var(--color-neutral-500); }
.cf-add button:hover { background: var(--color-neutral-200);
                       color: var(--color-neutral-800); }
.cf-add.open { display: flex; gap: 4px; }
.cf-add input { flex: 1 1 auto; min-width: 0; font: inherit; font-size: .76rem;
                padding: 4px 6px; border-radius: 6px;
                border: 1px solid var(--color-neutral-300);
                background: var(--color-neutral-100); color: inherit; }

/* ── on the glass ────────────────────────────────────────────────────────
   Every colour written out, and it has to be. This card is anchored to the
   search bar, which puts it OUTSIDE `.shell` — and `.shell` is where the
   glass scheme redefines --color-neutral-*. So the panel inherits the light
   theme's inks while sitting on a dark translucent panel: dark grey type on
   dark glass, which is roughly what it looked like. The interview's card
   has the same anchor and the same list of explicit colours for the same
   reason. */
body.glass .cfpane { background: rgba(26,24,22,.88);
                     border-color: rgba(255,255,255,.16);
                     box-shadow: inset 0 1px 0 rgba(255,255,255,.10),
                                 0 14px 40px rgba(0,0,0,.42); }
body.glass .cf-who b     { color: #f4f3f0; }
body.glass .cf-count,
body.glass .cf-loading,
body.glass .cf-add button { color: rgba(253,253,252,.66); }
body.glass .cf-go        { color: #fdfdfc; }
body.glass .cf-item.done .cf-go { color: rgba(244,243,240,.48); }
body.glass .cf-add button:hover  { color: rgba(244,243,240,.92); }
body.glass .cf-drop      { color: rgba(244,243,240,.45); }
body.glass .cf-drop:hover { color: #e3706c; }
body.glass .cf-col.empty .cf-phase { color: rgba(244,243,240,.7); }
body.glass .cf-face { background: rgba(255,255,255,.16); color: #f4f3f0; }
body.glass .cf-col { background: rgba(255,255,255,.06);
                     border-color: rgba(255,255,255,.14); }
body.glass .cf-phase { background: rgba(255,255,255,.10); }
/* #2f6b46 and #a13a3a are inks for a white panel and go muddy on the glass. */
/* Stronger than the first attempt. These bands sit on a translucent panel
   over a photograph, so a tint that reads clearly on a white mock-up is a
   suggestion of a colour by the time it is over the wall. */
body.glass .cf-col.done .cf-phase { background: rgba(122,196,152,.24); color: #bce8cd; }
body.glass .cf-col.open .cf-phase { background: rgba(227,112,108,.24); color: #f5aeaa; }
body.glass .cf-col.done { border-color: rgba(122,196,152,.30); }
body.glass .cf-col.open { border-color: rgba(227,112,108,.28); }
body.glass .cf-go:hover, body.glass .cf-add button:hover,
body.glass .cf-btn:hover { background: rgba(255,255,255,.12); }
body.glass .cf-btn { background: rgba(255,255,255,.08);
                     border-color: rgba(255,255,255,.16);
                     color: rgba(244,243,240,.9); }
body.glass .cf-tick { border-color: rgba(255,255,255,.34); color: transparent; }
body.glass .cf-item.done .cf-tick { background: #4f9b70; border-color: #4f9b70; }
body.glass .cf-add input { background: rgba(255,255,255,.08);
                           border-color: rgba(255,255,255,.16); }
body.glass .cf-ask { color: rgba(253,253,252,.8); }
body.glass .cf-yes { background: rgba(227,112,108,.2); color: #f5aeaa;
                     border-color: rgba(227,112,108,.4); }
body.glass .cf-yes:hover { background: rgba(227,112,108,.32); }
body.glass .cf-no  { color: rgba(253,253,252,.62); }
body.glass .cf-no:hover { background: rgba(255,255,255,.12); color: #fdfdfc; }

/* ── the index of blanks ────────────────────────────────────────────── */
.ivindex { position: absolute; right: calc(100% + clamp(8px, 1.4vw, 18px)); bottom: 0; z-index: 30;
           /* It grows UPWARDS from the bar, so its ceiling is what keeps it
              off the header. The bar sits a little over half way down the
              page; 40vh from there stops short of the top bar at every
              window height worth supporting, and the list inside scrolls
              rather than the panel growing past it. */
           width: clamp(148px, 17vw, 232px); max-height: 40vh;
           padding: var(--space-4); border-radius: 14px;
           background: rgba(255,255,255,.55);
           border: 1px solid var(--color-neutral-200);
           -webkit-backdrop-filter: blur(22px) saturate(1.2);
                   backdrop-filter: blur(22px) saturate(1.2);
           box-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 8px 28px rgba(28,30,33,.12);
           text-align: left; }
.ivindex-head { display: flex; align-items: baseline; justify-content: space-between;
                font-size: .7rem; text-transform: uppercase; letter-spacing: .05em;
                color: var(--color-neutral-500); margin-bottom: 5px; }
.ivindex-count { font-variant-numeric: tabular-nums; letter-spacing: 0;
                 color: var(--color-neutral-700); font-weight: 600; }
.ivindex-bar { height: 3px; border-radius: 2px; background: var(--color-neutral-200);
               overflow: hidden; margin-bottom: var(--space-2); }
.ivindex-bar span { display: block; height: 100%; background: #2f6b46;
                    transition: width .18s ease; }
/* Scrolls, because a demand has more blanks than fit beside a search bar. */
/* Sized by its content up to a ceiling, not by flex. `flex: 1 1 auto` with
   `min-height: 0` in a column whose own height is auto has nothing to
   distribute, so the list collapsed to nothing and the panel came out 37px
   tall with twelve rows inside it. */
.ivindex-list { max-height: 29vh; overflow-y: auto; margin: 0 -4px; padding: 0 4px; }

.ivrow { display: flex; align-items: baseline; gap: 7px; width: 100%;
         padding: 5px 6px; border: 0; border-radius: 7px; background: transparent;
         font: inherit; font-size: .76rem; color: inherit; text-align: left;
         cursor: pointer; }
.ivrow:hover { background: var(--color-neutral-100); }
.ivrow.at { background: var(--color-neutral-200); font-weight: 600; }
.ivrow-n { flex: 0 0 1.1rem; font-variant-numeric: tabular-nums;
           color: var(--color-neutral-500); font-size: .7rem; }
.ivrow-what { flex: 1 1 auto; min-width: 0; }
.ivrow-val { display: block; font-weight: 400; font-size: .68rem;
             color: var(--color-neutral-500); overflow: hidden;
             text-overflow: ellipsis; white-space: nowrap; }
.ivrow-tick { flex: 0 0 auto; color: #2f6b46; font-weight: 700; }
.ivrow.done .ivrow-what { color: var(--color-neutral-600); }
.ivrow.opt .ivrow-n { opacity: .5; }

/* Answered in somebody's own words, kept as typed, and still a blank in the
   letter. Amber, between the green tick and nothing: attended to, not
   finished.

   Written out rather than taken from --color-accent, which is #1c1e21 in
   this theme — near black, and invisible as a mark on the glass. */
.ivrow.kept .ivrow-tick { color: #a4702c; }
.ivrow.kept .ivrow-val  { font-style: italic; }

.ivmain { min-width: 0; }

.ivhead { display: flex; align-items: center; gap: var(--space-2);
          margin-bottom: 6px; }
.ivhead .ivwho { flex: 1 1 auto; min-width: 0; }
.ivwho { font-weight: 600; font-size: .9rem; }
.ivcount { font-size: .72rem; color: var(--color-neutral-500);
           font-variant-numeric: tabular-nums; }
.ivbar { height: 3px; border-radius: 2px; background: var(--color-neutral-200);
         overflow: hidden; margin-bottom: var(--space-3); }
.ivbar span { display: block; height: 100%; background: var(--color-accent, #c8663a);
              transition: width .18s ease; }
/* Keep and discard, in the header's top corner. */
.ivbtn { flex: 0 0 auto; width: 26px; height: 26px; padding: 0; border: 0;
         border-radius: 7px; background: transparent; cursor: pointer;
         display: grid; place-items: center; color: var(--color-neutral-500); }
.ivbtn svg { width: 15px; height: 15px; fill: none; stroke: currentColor;
             stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.ivbtn:hover { background: var(--color-neutral-200); color: var(--color-neutral-800); }
/* Red at rest, not only on approach. It was grey until hover on the theory
   that a red control invites the click — asked for the other way round, and
   the argument for it is better: the one button here that throws work away
   should look different from the one that keeps it, before the pointer is
   anywhere near either. */
.ivbtn-x       { color: #b03636; }
.ivbtn-x:hover { background: rgba(176,54,54,.12); color: #c94a4a; }

/* ── the discard confirm ────────────────────────────────────────────────
   In the panel rather than in a modal over the page: it is a question
   about this draft, and the draft should stay visible while it is asked. */
.ivconfirm { margin: 0 0 var(--space-3); padding: var(--space-3);
             border-radius: 10px; background: rgba(176,54,54,.08);
             border: 1px solid rgba(176,54,54,.24); }
.ivconfirm-ask { margin: 0 0 3px; font-size: .92rem; font-weight: 600; color: #b03636; }
.ivconfirm-why { margin: 0 0 var(--space-3); font-size: .78rem; line-height: 1.5;
                 color: var(--color-neutral-700); }
.ivconfirm-never { display: flex; align-items: center; gap: 7px;
                   font-size: .76rem; color: var(--color-neutral-600);
                   margin-bottom: var(--space-2); cursor: pointer; }
.ivconfirm-never input { margin: 0; }
.ivdanger { background: #b03636; border-color: #b03636; color: #fff; }
.ivdanger:hover { background: #9c2e2e; border-color: #9c2e2e; }

/* Both panels leave together. */
.ivgoing { opacity: 0; transform: translateY(-6px);
           transition: opacity .24s ease, transform .24s ease;
           pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .ivgoing { transition: none; } }

body.glass .ivbtn:hover { background: rgba(255,255,255,.14);
                          color: rgba(244,243,240,.95); }
/* #b03636 is chosen for a white panel and goes muddy on the glass. */
body.glass .ivbtn-x       { color: #e3706c; }
body.glass .ivbtn-x:hover { background: rgba(227,112,108,.18); color: #f08b87; }
body.glass .ivconfirm { background: rgba(176,54,54,.16);
                        border-color: rgba(176,54,54,.34); }
body.glass .ivconfirm-why { color: rgba(244,243,240,.8); }
body.glass .ivconfirm-never { color: rgba(244,243,240,.72); }

.ivask { margin: 0 0 3px; font-size: 1rem; font-weight: 500; }
.ivhint { margin: 0 0 var(--space-3); font-size: .78rem; line-height: 1.5;
          color: var(--color-neutral-600); }

/* The letter. Serif and generously led, because it is prose being read
   rather than a field being filled. */
/* Sized by what is in it. A carrier's name is one line and gets a one-line
   box; only a question flagged `long` — prose, a paragraph of narrative —
   is given room to be a paragraph. */
.ivletter { padding: var(--space-3); border-radius: 10px;
            background: var(--color-neutral-100);
            border: 1px solid var(--color-neutral-200);
            font-family: Georgia, 'Times New Roman', serif; font-size: .88rem;
            line-height: 1.65; white-space: pre-wrap; }
.ivletter.long { min-height: 7.5em; }
.ivctx { color: var(--color-neutral-500); margin-bottom: 4px; }
.ivline { color: var(--color-neutral-900); }

/* Where the answer goes. Underlined rather than boxed, so it reads as a
   blank on a page and not as a form control sitting in a letter. */
.ivhole { border-bottom: 2px solid var(--color-accent, #c8663a);
          padding: 0 3px; min-width: 4ch; display: inline-block; }
.ivhole.filled { background: rgba(200,102,58,.10); border-radius: 3px 3px 0 0; }
.ivblank { color: var(--color-neutral-500); font-style: italic; }

.ivreads { margin: var(--space-2) 0 0; font-size: .78rem; color: #2f6b46; }

/* What the interview says when it could not read an answer. Deliberately
   the quiet grey of a note and not the red of an error: the answer is
   accepted, it is being kept as typed, and the only thing being reported
   is that the letter keeps a blank there. Red would say "you are wrong"
   about words that are usually right. */
.ivkept  { margin: var(--space-2) 0 0; font-size: .78rem; line-height: 1.5;
           color: var(--color-neutral-600); }
.ivgaps  { margin: var(--space-3) 0 0; font-size: .74rem; line-height: 1.5;
           color: var(--color-neutral-500); }

.ivkeys { display: flex; flex-wrap: wrap; gap: var(--space-3);
          margin-top: var(--space-3); font-size: .72rem; color: var(--color-neutral-500); }
.ivkeys kbd { font-family: ui-monospace, Menlo, monospace; font-size: .68rem;
              padding: 1px 5px; border-radius: 4px; margin-right: 3px;
              background: var(--color-neutral-200); color: var(--color-neutral-700); }
.ivstop { margin-left: auto; cursor: pointer; text-decoration: underline; }
.ivstop:hover { color: var(--color-neutral-800); }

/* ── legible over a photograph ────────────────────────────────────────────
   These cards were 7% white over whatever the backdrop happened to be, which
   is a sheet of glass with the room showing through it. Fine over the plain
   wall it was designed on, and unreadable over a creek: the type had to
   compete with grass and moving water two pixels behind it.

   So the glass is a dark ground rather than a transparency — still blurred,
   still clearly a pane in front of the room, but with a floor under the type
   that does not move. Every colour inside is written out rather than left to
   a variable, because these cards are anchored to the search bar and that
   puts them OUTSIDE `.shell`, where the glass scheme redefines its inks. */
body.glass .ivpane { background: rgba(26,24,22,.88);
                     border-color: rgba(255,255,255,.16);
                     box-shadow: inset 0 1px 0 rgba(255,255,255,.10),
                                 0 14px 40px rgba(0,0,0,.42); }
body.glass .ivletter { background: rgba(255,255,255,.06);
                       border-color: rgba(255,255,255,.14); }
body.glass .ivline { color: #fdfdfc; }
body.glass .ivctx  { color: rgba(253,253,252,.62); }
body.glass .ivblank { color: rgba(253,253,252,.55); }
body.glass .ivask  { color: #fdfdfc; }
body.glass .ivwho  { color: #fdfdfc; }
body.glass .ivhint,
body.glass .ivgaps,
body.glass .ivkeys,
body.glass .ivstop { color: rgba(253,253,252,.68); }
body.glass .ivstop:hover { color: #fdfdfc; }
body.glass .ivbtn { color: rgba(253,253,252,.72); }
/* Both of these are dark inks chosen for a white panel, and the pane over
   the glass is not one. Left alone, "Kept as written" was charcoal grey on
   a photograph. */
body.glass .ivkept  { color: rgba(244,243,240,.72); }
body.glass .ivreads { color: #9ad6b0; }
body.glass .ivkeys kbd { background: rgba(255,255,255,.12);
                         color: rgba(244,243,240,.85); }

body.glass .ivindex { background: rgba(26,24,22,.88);
                      border-color: rgba(255,255,255,.16);
                      box-shadow: inset 0 1px 0 rgba(255,255,255,.10),
                                  0 14px 40px rgba(0,0,0,.42); }
body.glass .ivindex-head { color: rgba(253,253,252,.6); }
body.glass .ivrow-what   { color: #fdfdfc; }
body.glass .ivrow.done .ivrow-what { color: rgba(253,253,252,.62); }
body.glass .ivrow-n,
body.glass .ivrow-val    { color: rgba(253,253,252,.55); }
body.glass .ivrow:hover { background: rgba(255,255,255,.07); }
body.glass .ivrow.at { background: rgba(255,255,255,.12); }
body.glass .ivrow.kept .ivrow-tick { color: rgba(235,190,128,.95); }

/* Zoom and a narrow window are the same thing to a media query: zooming in
   shrinks the CSS viewport, so a breakpoint that reflowed at 1100px moved
   the index from beside the bar to above it the moment somebody zoomed.
   The arrangement has to hold still.

   So it SHRINKS rather than reflowing — clamped width, and the list scrolls
   — and keeps its place beside the bar at every size a desktop reaches by
   zooming. Only a genuinely tiny viewport stacks it, well below where zoom
   on a laptop lands. */
/* Making room, rather than moving anything.
   The bar is min(660px, 92vw) and centred, so below about 1000px there is
   not enough beside it for the index — which went off the left edge at
   -128px instead of reorienting. Narrowing the BAR while an interview is
   running keeps every panel in the same relative place at every zoom
   level: index left, letter above, bar under them both. The bar changes
   width; it does not change position, and nothing swaps places. */
body.interviewing .bigsearch2 { width: min(660px, calc(92vw - 290px)); }

@media (max-width: 520px) {
  .ivindex { position: static; width: auto; max-height: none;
             margin-bottom: 10px; }
  .ivindex-list { max-height: 132px; }
  .ivpane { position: static; margin-bottom: 10px; }
}
@media (max-width: 620px) {
  .ivletter { font-size: .84rem; }
  .ivkeys { gap: var(--space-2); }
}

/* ── the calendar ───────────────────────────────────────────────────────
   A month and the day picked out of it, side by side on a desk and stacked
   on a phone. Six rows always, so paging through the year does not change
   the height of the grid and move everything under it. */
.remwrap { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
           gap: var(--space-5); margin-top: var(--space-4); align-items: start; }
@media (max-width: 900px) { .remwrap { grid-template-columns: 1fr; } }

/* ── the sheet the calendar sits on ─────────────────────────────────────

   Three layers, and the middle one is the point: the photograph, then a
   pane of fogged glass, then the grid. A calendar is forty-two boxes and a
   lot of thin lines, and reading thin lines against a photograph is work —
   the eye keeps refocusing between the numbers and whatever is behind them.

   The glass is what stops that. It does not hide the backdrop, it puts
   distance between the backdrop and the grid, which is the same thing a
   frosted pane does in a door.

   The recipe is the one already used by the search bar and the history
   menu — rgba white in the fifties to seventies, blur with a little
   saturation to stop the blur going grey, and an inset highlight along the
   top edge so the pane has a lit edge rather than being a flat rectangle.
   Matching it matters more than tuning it: two different glasses on one
   screen read as a mistake. */
.remsheet {
  padding: var(--space-4);
  border-radius: 16px;
  background: var(--color-neutral-100);
  border: 1px solid var(--color-neutral-200);
}

/* Only fogged when there is something to fog. On a plain background a
   backdrop-filter blurs a flat colour, which costs a compositor layer to
   produce no visible difference.

   TWO recipes, and they are not interchangeable — the first draft used the
   light one everywhere and made the calendar almost unreadable in glass
   mode. White at 58% over a dark backdrop is a LIGHT surface, and the glass
   theme writes in light type. Light on light. The fog has to follow the
   type, not the other way round. */

/* Light theme with a photograph behind it: white glass, dark type. This is
   the search bar's recipe, unchanged. */
body.has-backdrop .remsheet {
  background: rgba(255, 255, 255, .58);
  -webkit-backdrop-filter: blur(24px) saturate(1.25);
          backdrop-filter: blur(24px) saturate(1.25);
  border-color: rgba(255, 255, 255, .62);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .8), 0 8px 30px rgba(28, 30, 33, .12);
}

/* Glass theme: the surface stays dark, so the fog is a thin white veil that
   lifts the pane off the wall without turning it into a light box. */
body.glass .remsheet {
  background: rgba(255, 255, 255, .07);
  -webkit-backdrop-filter: blur(24px) saturate(1.2);
          backdrop-filter: blur(24px) saturate(1.2);
  border-color: rgba(255, 255, 255, .14);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .16), 0 10px 34px rgba(0, 0, 0, .22);
}

/* The panels that sit ON the glass go quiet, because they were drawn to
   stand out from a page and are now standing out from a pane already doing
   that job. Two raised surfaces stacked is the busy-ness the glass was
   added to remove. */
body.has-backdrop .remsheet .remitem,
body.has-backdrop .remsheet .remcompose {
  background: rgba(255, 255, 255, .42);
  border-color: rgba(255, 255, 255, .55);
}
body.glass .remsheet .remitem,
body.glass .remsheet .remcompose {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .12);
}
body.has-backdrop .remsheet .calday { border-color: rgba(255, 255, 255, .5); }
body.glass .remsheet .calday { border-color: rgba(255, 255, 255, .13); }

body.has-backdrop .remsheet .calday:hover,
body.has-backdrop .remsheet .calday.picked { background: rgba(255, 255, 255, .5); }
body.glass .remsheet .calday:hover,
body.glass .remsheet .calday.picked { background: rgba(255, 255, 255, .11); }

/* A day outside the month was already dimmed to .38 against a page. On
   glass that lands under the threshold where a number is a number, so it is
   lifted — still clearly not this month, still legible. */
body.glass .remsheet .calday.outside,
body.has-backdrop .remsheet .calday.outside { opacity: .5; }

/* On a phone the row has three columns competing for 375px: a time, the
   thing itself, and two buttons. The title lost, and "Monday standup" came
   out over three lines with the buttons stacked beside it. Below the break
   the time sits above the title and the buttons go on one line underneath,
   which is the same information in the order somebody reads it. */
@media (max-width: 620px) {
  .remitem { flex-direction: column; gap: 6px; }
  .remwhen { flex: 0 0 auto; font-weight: 500; color: var(--color-neutral-700); }
  .remacts { flex-direction: row; gap: 8px; }
}
body.glass .remwhen { color: rgba(244, 243, 240, .72); }

.calhead { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); }
.calhead h3 { margin: 0; font-size: 1.05rem; flex: 1 1 auto; }

.calgrid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.calname { font-size: .68rem; text-transform: uppercase; letter-spacing: .06em;
           color: var(--color-neutral-500); text-align: center; padding-bottom: 4px; }
.calday { position: relative; min-height: 62px; padding: 6px; border-radius: 9px;
          border: 1px solid var(--color-neutral-200); background: transparent;
          font: inherit; color: inherit; cursor: pointer; text-align: left;
          display: flex; flex-direction: column; gap: 4px; }
.calday:hover { background: var(--color-neutral-100); }
.calday.outside { opacity: .38; }
.calday.istoday { border-color: var(--color-accent, #c8663a); }
.calday.picked { background: var(--color-neutral-100);
                 box-shadow: inset 0 0 0 2px var(--color-accent, #c8663a); }
.calnum { font-size: .8rem; font-variant-numeric: tabular-nums; }
.caldots { display: flex; flex-wrap: wrap; gap: 3px; align-items: center; }
.caldot { width: 6px; height: 6px; border-radius: 50%; background: var(--color-neutral-500); }
.caldot.meet { background: #2f6b46; }
.calmore { font-size: .62rem; color: var(--color-neutral-500); }

.remday-head { display: flex; align-items: center; justify-content: space-between;
               gap: var(--space-3); margin-bottom: var(--space-3); }
.remday-head h3 { margin: 0; font-size: 1rem; }

.remitem { display: flex; gap: var(--space-3); padding: var(--space-3);
           border-radius: 11px; margin-bottom: 8px;
           background: var(--color-neutral-100);
           border: 1px solid var(--color-neutral-200); }
.remitem.meeting { border-left: 3px solid #2f6b46; }
.remitem.off { opacity: .55; }
.remwhen { flex: 0 0 5.6rem; font-size: .78rem; font-variant-numeric: tabular-nums;
           color: var(--color-neutral-600); }
.rembody { flex: 1 1 auto; min-width: 0; }
.remtitle { font-size: .9rem; font-weight: 500; }
.remoff { font-weight: 400; font-size: .74rem; color: #b03636; }
.remdetail, .rempeople, .remplace { font-size: .78rem; color: var(--color-neutral-600);
                                    margin-top: 3px; line-height: 1.45; }
.remcase { display: inline-block; margin-top: 4px; padding: 0; border: 0;
           background: none; font: inherit; font-size: .78rem;
           color: var(--color-accent, #c8663a); cursor: pointer; text-align: left; }
.remacts { flex: 0 0 auto; display: flex; flex-direction: column; gap: 4px; }

.remcompose { padding: var(--space-4); border-radius: 12px; margin-bottom: var(--space-4);
              background: var(--color-neutral-100);
              border: 1px solid var(--color-neutral-200); }
.remcompose h4 { margin: 0 0 var(--space-3); font-size: .78rem; text-transform: uppercase;
                 letter-spacing: .05em; color: var(--color-neutral-500); }
.remcompose .input, .remcompose textarea { width: 100%; margin-bottom: var(--space-2); }
.remcompose textarea { font: inherit; font-size: .86rem; line-height: 1.5; padding: 9px;
                       border-radius: 9px; border: 1px solid var(--color-neutral-300);
                       background: var(--color-bg, #fff); color: inherit; resize: vertical; }
.remkinds, .rempick { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--space-3); }
.remrow { display: flex; gap: var(--space-3); }
.remrow label, .remplacein { display: flex; flex-direction: column; gap: 3px; flex: 1 1 0; min-width: 0; }
.remrow span, .remlabel, .remplacein span { font-size: .7rem; text-transform: uppercase;
                                            letter-spacing: .05em; color: var(--color-neutral-500); }
.remlabel { flex: 0 0 100%; }
.remread { margin: 0 0 var(--space-3); font-size: .76rem; color: var(--color-neutral-600); }
.remread.good  { color: #2f6b46; }
.remread.guess { color: #8a5a1c; }
.remread.bad   { color: #b03636; }

body.glass .calday { border-color: rgba(255,255,255,.12); }
body.glass .calday:hover, body.glass .calday.picked { background: rgba(255,255,255,.07); }
body.glass .remitem, body.glass .remcompose { background: rgba(255,255,255,.05);
                                              border-color: rgba(255,255,255,.12); }

/* ── Reminders: a neutral palette, on this screen only ───────────────────

   The grey scheme from Daytona's reference, and it runs LIGHT: Grey Nickel
   windows carrying black text, on a black ground. Scoped to .remsheet and
   written as local custom properties, so nothing here can reach the rest of
   the application — :root is untouched and every other screen is as it was.

   OPAQUE ON PURPOSE, and this is the part that matters. The glass recipe
   above fogs the photograph; it does not remove it. Composite any grey over
   a sunlit film and you get the film's luminance, not the grey's — which is
   why the top-right of the grid washed out wherever the sun sat behind it.
   Applying a palette over glass would reproduce that wash in a different hue
   and the trial would tell us nothing.

   The comment at the head of the glass recipe says the fog exists to stop
   the eye refocusing between thin lines and a photograph. That still holds —
   a calendar is forty-two boxes and a lot of thin lines. An opaque panel
   answers it more completely than fogging does, so this carries the same
   argument further rather than reversing it.

   Both theme branches are restated. `has-backdrop` is set by nothing in the
   application any more — the body is always `glass` — but the rules for it
   are still in the stylesheet and would win the moment it came back, so it
   is overridden here rather than left as a trap.
   ===================================================================== */
.remsheet {
  --rem-panel:  #c3c3bd;   /* Grey Nickel   — the window itself */
  --rem-cell:   #adada7;   /* Cool Granite  — day cells, rows, compose box */
  --rem-line:   #82827d;   /* Grey Pepper   — borders, hover, the picked day */
  --rem-muted:  #4e4f4d;   /* Black Oak     — outside the month, hints */
  --rem-second: #212527;   /* Washed Black  — second-rank text */
  --rem-text:   #0a0c0d;   /* Black Wash    — everything else */
  background: var(--rem-panel);
  border-color: var(--rem-line);
  color: var(--rem-text);
}

/* Both themes, one answer: no fog on this screen. */
body.has-backdrop .remsheet,
body.glass .remsheet {
  background: var(--rem-panel);
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
  border-color: var(--rem-line);
  box-shadow: 0 10px 34px rgba(0, 0, 0, .45);
}

/* Measured, so none of this is guesswork. Black Wash on the window is 11.07
   and on a cell 8.69; Washed Black is 8.73 and 6.85. The one figure below AA
   is Black Oak on a cell at 3.65, which is why the days outside the month
   sit on the WINDOW colour instead — 4.65 there — rather than being dimmed
   on top of an already-quiet grey. */
/* Every heading on the sheet, not just the calendar's. `body.glass h3` sets
   the application's off-white, and the day panel's own heading matched
   nothing more specific — so "Sep 6, 2026" came out near-white on a
   near-white panel. Two classes here beats that rule's one. */
.remsheet h2,
.remsheet h3,
.remsheet h4,
.remsheet .section-title { color: var(--rem-text); }
.remsheet .calhead h3 { color: var(--rem-text); }
.remsheet .calname    { color: var(--rem-second); }

.remsheet .calday {
  background: var(--rem-cell);
  border-color: var(--rem-line);
  color: var(--rem-text);
}
body.has-backdrop .remsheet .calday,
body.glass .remsheet .calday { border-color: var(--rem-line); }
.remsheet .calday:hover,
body.glass .remsheet .calday:hover { background: var(--rem-line); }

/* One dimming mechanism, not two. These were .38 against a page and lifted
   to .5 on glass; against an opaque panel the colour carries it on its own,
   and opacity stacked on an already-quiet grey put the number under the
   floor. */
.remsheet .calday.outside,
body.glass .remsheet .calday.outside,
body.has-backdrop .remsheet .calday.outside {
  opacity: 1;
  color: var(--rem-muted);
  background: var(--rem-panel);
}

/* Today and picked carried the orange accent, and there is no accent in this
   palette. Black does both jobs, and they are told apart by weight rather
   than by hue: today is a one-pixel ring on an ordinary cell, the picked day
   is a filled cell inside a two-pixel one.

   The theme-scoped forms are not optional — the border rule above is
   `body.glass .remsheet .calday`, which carries an element as well as three
   classes and so out-specifies a bare `.remsheet .calday.istoday`. Without
   them today's ring came out the same grey as every other cell border:
   invisible, and only not noticed because today happened to be the picked
   day as well. */
.remsheet .calday.istoday,
body.glass .remsheet .calday.istoday,
body.has-backdrop .remsheet .calday.istoday { border-color: var(--rem-text); }
.remsheet .calday.picked,
body.glass .remsheet .calday.picked,
body.has-backdrop .remsheet .calday.picked {
  background: var(--rem-line);
  box-shadow: inset 0 0 0 2px var(--rem-text);
}
.remsheet .calnum  { color: inherit; }
.remsheet .calmore { color: var(--rem-muted); }
.remsheet .caldot  { background: var(--rem-second); }

/* ── the one colour on the screen, kept ──────────────────────────────────

   Green marks the thing somebody else asked you to be at, as against a
   reminder you set yourself. Daytona's call: it stays exactly as it was.

   Restated rather than simply left alone, because leaving it alone did not
   work. `.caldot.meet` and `.remsheet .caldot` are both two classes and the
   palette block is later in the file, so the grey won on source order and
   every meeting dot came out the same as a personal one. The row stripe went
   the same way for a different reason: `border-color` on
   `body.glass .remsheet .remitem` sets all four sides and out-specifies the
   `border-left` shorthand on `.remitem.meeting`.

   It reads better here than it did on the dark scheme, as it happens: 3.58
   against the window where it was 2.44 against a dark cell. */
.remsheet .caldot.meet,
body.glass .remsheet .caldot.meet { background: #2f6b46; }
.remsheet .remitem.meeting,
body.glass .remsheet .remitem.meeting,
body.has-backdrop .remsheet .remitem.meeting { border-left-color: #2f6b46; }

/* The panels on the sheet, restated — the rgba-white versions above were
   drawn to sit on glass. */
body.has-backdrop .remsheet .remitem,
body.has-backdrop .remsheet .remcompose,
body.glass .remsheet .remitem,
body.glass .remsheet .remcompose {
  background: var(--rem-cell);
  border-color: var(--rem-line);
}

/* ── the rest of the sheet ───────────────────────────────────────────────

   The palette covers the calendar. Everything else on the screen — the
   reminder list, the compose form, and every control in it — was drawn for
   light type on dark glass, and all of it is now sitting on a light panel.
   Left alone it would be white on near-white, which would make the trial
   worthless in the same way that leaving the glass on would: the screen
   would be judged on text nobody could read rather than on the palette.
   ===================================================================== */
.remsheet .remwhen,
body.glass .remsheet .remwhen { color: var(--rem-second); }
.remsheet .remdetail,
.remsheet .rempeople,
.remsheet .remplace  { color: var(--rem-second); }
.remsheet .remcompose h4,
.remsheet .remrow span,
.remsheet .remlabel,
.remsheet .remplacein span { color: var(--rem-second); }
/* `body.glass .hint` carries an element as well, so the bare form loses and
   the empty-day line came out in the application's warm off-white. */
.remsheet .hint,
body.glass .remsheet .hint { color: var(--rem-muted); }

/* The case link carried --color-accent. It is a link, so it takes the plain
   black and says so with an underline on hover rather than with a colour
   this palette does not have. */
.remsheet .remcase { color: var(--rem-text); }
.remsheet .remcase:hover { text-decoration: underline; }

/* The three semantic colours say something the greys cannot — how well what
   you typed was understood, and whether a reminder is switched off — so the
   hue is kept and only the lightness moves. Darkened for a light ground:
   4.56, 4.79 and 4.50 against a cell. */
.remsheet .remoff       { color: #7a1f1f; }
.remsheet .remread      { color: var(--rem-second); }
.remsheet .remread.good { color: #1e452e; }
.remsheet .remread.guess{ color: #5c3a10; }
.remsheet .remread.bad  { color: #7a1f1f; }

/* ── the controls ────────────────────────────────────────────────────────
   Fields go to the window colour rather than the cell's, so a place you type
   reads as a well cut into the box holding it. Everything here has to beat
   `body.glass .input` and friends, which is why each one carries the theme
   class as well. */
.remsheet .remcompose textarea,
body.glass .remsheet .remcompose textarea,
body.glass .remsheet .input,
body.glass .remsheet .select,
body.glass .remsheet textarea.input {
  background: var(--rem-panel);
  border-color: var(--rem-line);
  color: var(--rem-text);
  caret-color: var(--rem-text);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .30);
}
body.glass .remsheet .input::placeholder,
body.glass .remsheet .remcompose textarea::placeholder { color: var(--rem-muted); }
body.glass .remsheet .input:focus-visible,
body.glass .remsheet .select:focus-visible,
body.glass .remsheet .remcompose textarea:focus-visible {
  border-color: var(--rem-text);
  background: var(--rem-panel);
}

body.glass .remsheet .chip { border-color: var(--rem-line); color: var(--rem-second); }
body.glass .remsheet .chip:hover { color: var(--rem-text); border-color: var(--rem-text); }
body.glass .remsheet .chip.on {
  background: var(--rem-text); border-color: var(--rem-text); color: var(--rem-panel);
}

/* The one filled button on the screen is black on grey, which is the whole
   palette doing the job an accent colour used to do. */
body.glass .remsheet .btn-primary {
  background: var(--rem-text); border-color: var(--rem-text); color: var(--rem-panel);
}
body.glass .remsheet .btn-primary:hover:not(:disabled) { background: var(--rem-second); }
body.glass .remsheet .btn-ghost { color: var(--rem-second); }
body.glass .remsheet .btn-ghost:hover {
  background: rgba(10, 12, 13, .10); color: var(--rem-text);
}
body.glass .remsheet .btn-secondary { border-color: var(--rem-line); color: var(--rem-text); }
body.glass .remsheet .btn-secondary:hover:not(:disabled) { background: rgba(10, 12, 13, .08); }


/* The panel itself has padding: 0, like the private panel it sits beside,
   so the open body carries its own — otherwise every line runs into the
   edge of the card. */
.portal-body { padding: var(--space-4); }
.portal-lede { margin: 0 0 var(--space-4); font-size: 13.5px; line-height: 1.55;
               color: var(--color-neutral-600); max-width: 62ch; }

.portal-access { margin: var(--space-3) 0; padding: 10px 12px; border-radius: 10px;
                 background: var(--color-neutral-100);
                 border: 1px solid var(--color-neutral-200); font-size: .84rem;
                 color: var(--color-neutral-700); }
.portal-access p { margin: 0 0 var(--space-2); }
.portal-locked { color: #8a2f2f; font-weight: 500; }

/* Shown once. Loud on purpose — this is the only moment the code exists in
   a readable form anywhere. */
.portal-fresh { margin: var(--space-3) 0; padding: var(--space-3) var(--space-4);
                border-radius: 12px; background: #fdf6ee;
                border: 1px solid #e0c9a8; border-left: 3px solid #6b4423; }
.portal-fresh h4 { margin: 0 0 4px; font-size: .92rem; color: #5a3a1c; }
.portal-fresh p { margin: 0 0 var(--space-3); font-size: .8rem; line-height: 1.5;
                  color: #6b5340; }
.portal-copy { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.portal-copy > span { flex: 0 0 auto; width: 9rem; font-size: .76rem; color: #6b5340; }
.portal-copy input { flex: 1 1 auto; min-width: 0; font: inherit;
                     font-family: ui-monospace, Menlo, monospace; font-size: .8rem;
                     padding: 7px 9px; border-radius: 8px;
                     border: 1px solid #e0c9a8; background: #fff; color: #3a2a18; }
.portal-why { margin: var(--space-2) 0 0; font-size: .76rem; line-height: 1.5;
              color: var(--color-neutral-600); }
.portal-warn { margin: var(--space-2) 0; padding: 9px 11px; border-radius: 9px;
               background: var(--color-neutral-100);
               border-left: 3px solid #c8663a; font-size: .78rem; line-height: 1.5;
               color: var(--color-neutral-800); }

/* ── what the case is waiting on ───────────────────────────────────────
   Above the composer, because it is the thing that should be current and
   the thing somebody is most likely to have come here to change. Writing a
   note is the occasional act; keeping this true is the habit. */
.portal-waiting { margin-top: var(--space-4); }
.portal-dates { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
@media (max-width: 520px) { .portal-dates { grid-template-columns: 1fr; } }

/* The facts the client's page carries without anybody writing them. Flat
   rows, not cards: there is nothing to do to any of them. */
.portal-milestone {
  display: flex; gap: var(--space-3); align-items: baseline;
  padding: 6px 0; border-bottom: 1px solid var(--color-divider);
  font-size: .8rem;
}
.portal-milestone:last-child { border-bottom: 0; }
.portal-ms-when {
  flex: 0 0 6.5rem; color: var(--color-neutral-500);
  font-variant-numeric: tabular-nums;
}
.portal-ms-what { color: var(--color-neutral-800); }

.portal-compose { margin-top: var(--space-4); }
.portal-compose h4, .portal-list h4, .portal-waiting h4 {
  margin: 0 0 var(--space-2); font-size: .78rem; text-transform: uppercase;
  letter-spacing: .05em; color: var(--color-neutral-500);
}
.portal-kinds { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--space-3); }
.portal-compose textarea {
  width: 100%; font: inherit; font-size: .88rem; line-height: 1.6; padding: 10px;
  border-radius: 10px; border: 1px solid var(--color-neutral-300);
  background: var(--color-neutral-100); color: inherit; resize: vertical;
}
.portal-compose textarea:focus { outline: none; border-color: var(--color-accent, #c8663a); }

.portal-list { margin-top: var(--space-4); }
.portal-item { padding: var(--space-3); border-radius: 11px; margin-bottom: 8px;
               background: var(--color-neutral-100);
               border: 1px solid var(--color-neutral-200); }
/* What a client can read right now is the thing to be able to see at a
   glance, so it is the only one that is marked. */
.portal-item.live { border-left: 3px solid #2f6b46; background: var(--color-surface); }
.portal-item-head { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.portal-kind { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
               color: var(--color-neutral-500); }
.portal-item strong { display: block; font-size: .9rem; margin-bottom: 3px; }
.portal-item p { margin: 0; font-size: .82rem; line-height: 1.55;
                 color: var(--color-neutral-700); white-space: pre-wrap; }
.tag-draft { background: var(--color-neutral-200); color: var(--color-neutral-700); }

@media (max-width: 620px) {
  .portal-copy { flex-wrap: wrap; }
  .portal-copy > span { width: 100%; }
}

/* ── the System screen ─────────────────────────────────────────────────
   Dense on purpose. This is read by somebody looking for one line that is
   wrong, so the columns line up, the numbers are tabular, and the rows
   worth a second look are marked rather than merely present. */
.opswindows { display: flex; gap: 6px; flex-wrap: wrap; margin: var(--space-3) 0; }
.opsbody { margin-top: var(--space-4); }

/* Six columns of short numbers comes out around 457px, and on a phone that
   is wider than the viewport. With no scroller of its own it widened the
   whole page, and the browser answered by zooming out to about 474px — so
   every screen rendered smaller than it should, and nothing looked broken
   enough for anybody to report it. The main table has solved this since it
   was written, with .table-wrap; this is the same fix for the one table
   that never got one. */
.opstable { width: 100%; border-collapse: collapse; margin-top: var(--space-3);
            font-size: .82rem; display: block; overflow-x: auto; }
.opstable th { text-align: left; font-weight: 600; font-size: .72rem;
               text-transform: uppercase; letter-spacing: .05em;
               color: var(--color-neutral-500); padding: 0 10px 7px 0;
               border-bottom: 1px solid var(--color-neutral-300); }
.opstable td { padding: 8px 10px 8px 0; border-bottom: 1px solid var(--color-neutral-200);
               color: var(--color-neutral-800); vertical-align: top; }
.opstable tbody tr:hover { background: var(--color-neutral-100); }

/* ── what has been checked ──────────────────────────────────────────────
   Four blocks rather than one list, because the four are different kinds of
   fact and running them together in one column is what makes somebody read
   a March answer as this morning's. */
.scangrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
            gap: var(--space-4); margin-top: var(--space-4); }
.scanblock { padding: var(--space-4); border-radius: 12px;
             background: var(--color-neutral-100);
             border: 1px solid var(--color-neutral-200); min-width: 0; }
.scanblock h4 { margin: 0 0 var(--space-3); font-size: .78rem; text-transform: uppercase;
                letter-spacing: .05em; color: var(--color-neutral-500); }
.scanline { display: flex; align-items: baseline; gap: var(--space-3);
            justify-content: space-between; padding: 6px 0;
            border-bottom: 1px solid var(--color-neutral-200); font-size: .84rem; }
.scanline:last-of-type { border-bottom: 0; }
.scanlabel { color: var(--color-neutral-700); flex: 0 1 auto; min-width: 0; }
.scanvalue { text-align: right; font-variant-numeric: tabular-nums;
             color: var(--color-neutral-900); flex: 1 1 auto; min-width: 0; }
.scan-ok   { color: #2f6b46; }
.scan-warn { color: #8a5a1c; }
.scan-bad  { color: #b03636; font-weight: 600; }
.scanwhen  { color: var(--color-neutral-500); font-size: .74rem; white-space: nowrap; }
.scannote  { margin: var(--space-3) 0 0; font-size: .76rem; line-height: 1.5;
             color: var(--color-neutral-600); }

body.glass .scanblock { background: rgba(255,255,255,.05);
                        border-color: rgba(255,255,255,.12); }
body.glass .scanline { border-color: rgba(255,255,255,.10); }
.ops-mono { font-family: ui-monospace, Menlo, monospace; font-size: .76rem;
            font-variant-numeric: tabular-nums; color: var(--color-neutral-600); }
.ops-sub { display: block; font-size: .72rem; color: var(--color-neutral-500);
           margin-top: 2px; }
.ops-p95 { font-weight: 600; font-variant-numeric: tabular-nums; }
.ops-bad td { background: rgba(176, 54, 54, .06); }
/* Refusals and bursts. Marked down the edge rather than coloured all over,
   so a page of them is still readable. */
.ops-loud td:first-child { box-shadow: inset 3px 0 0 #c8663a; }

/* One bar per minute. A chart library for this would be a dependency for
   something forty lines of CSS already does. */
.opsbars { display: flex; align-items: flex-end; gap: 1px; height: 90px;
           padding: 8px 10px; border-radius: 10px;
           background: var(--color-neutral-100);
           border: 1px solid var(--color-neutral-200); overflow: hidden; }
.opsbar { flex: 1 1 auto; min-width: 2px; background: var(--color-neutral-400);
          border-radius: 1px 1px 0 0; }
.opsbar:hover { background: var(--color-accent, #c8663a); }
.opsbar-err { background: #b03636; }

/* ── a held change of payment details ──────────────────────────────────
   Loud without being an error. Nothing has gone wrong; a control has done
   what it is for, and the work now is a phone call. */
.paychange { margin: var(--space-3) 0 0; padding: var(--space-3);
             border-radius: 11px; background: var(--color-neutral-100);
             border: 1px solid var(--color-neutral-300);
             border-left: 3px solid #c8663a; }
.paychange-late { border-left-color: #b03636; background: #fdf3ef; }
.paychange-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; }
.paychange-head strong { font-size: .86rem; color: var(--color-neutral-900); }
.paychange-head span { font-size: .74rem; color: var(--color-neutral-600); }
.paychange-late-why { margin: 8px 0 0; font-size: .78rem; line-height: 1.45;
                      color: #8a2f2f; font-weight: 500; }

/* Old and new side by side. Comparing four digits is what a person checking
   a change of instructions actually does. */
.paychange-diff { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 10px;
                  font-size: .8rem; color: var(--color-neutral-700); }
.paychange-diff b { font-variant-numeric: tabular-nums; color: var(--color-neutral-900); }

.paychange-call { margin-top: 10px; padding: 10px; border-radius: 9px;
                  background: var(--color-surface);
                  border: 1px solid var(--color-neutral-200); }
.paychange-call p { margin: 0 0 8px; font-size: .76rem; line-height: 1.45;
                    color: var(--color-neutral-700); }
.paychange-fields { display: flex; flex-wrap: wrap; gap: 6px; }
.paychange-fields .input { flex: 1 1 150px; min-width: 0; }
.paychange-done { margin-top: 10px; padding: 8px 10px; border-radius: 9px;
                  background: #dcefe2; color: #24603d; font-size: .78rem; }
.paychange-done span { display: block; margin-top: 3px; color: #2f6b46; opacity: .85; }
.paychange-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
                  margin-top: 10px; }
.paychange-blocked { flex: 1 1 100%; font-size: .74rem; line-height: 1.4;
                     color: var(--color-neutral-600); }
.paywarn { margin: var(--space-3) 0 0; padding: 10px 12px; border-radius: 9px;
           font-size: .8rem; line-height: 1.45; }
.paywarn-soft { background: var(--color-neutral-100);
                border: 1px solid var(--color-neutral-300);
                border-left: 3px solid #c8663a; color: var(--color-neutral-800); }

/* ── sessions and trusted browsers ─────────────────────────────────────
   A list somebody scans for the one line that is not them, so the columns
   line up and the current row is marked rather than merely present. */
.sesslist { display: grid; gap: 4px; margin-top: var(--space-2); }
.sessrow { display: flex; align-items: center; gap: 10px; padding: 8px 10px;
           border: 1px solid var(--color-neutral-200); border-radius: 9px;
           background: var(--color-neutral-100); font-size: .82rem; }
.sessrow.is-me { border-color: var(--color-neutral-400); }
.sessrow-wait { border-left: 3px solid #c8663a; }
.sess-what { flex: 1 1 40%; min-width: 0; color: var(--color-neutral-800);
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sess-me { margin-left: 8px; font-size: .66rem; text-transform: uppercase;
           letter-spacing: .05em; padding: 1px 6px; border-radius: 6px;
           background: var(--color-neutral-200); color: var(--color-neutral-700); }
.sess-where { flex: 0 0 150px; color: var(--color-neutral-600);
              font-variant-numeric: tabular-nums; }
.sess-when { flex: 0 0 130px; color: var(--color-neutral-500); }

/* The fingerprint. Monospace and tabular, because its whole job is being
   compared character by character against another screen — proportional
   digits make two different strings look alike at a glance, which is the
   one thing this column must never do. */
/* The row's own button. Given a floor because adding the fingerprint
   column squeezed it until "Sign this one out" broke across three lines,
   which reads as a broken control rather than a narrow one. */
.sessrow > .btn { flex: 0 0 auto; white-space: nowrap; }

.sess-print { flex: 0 0 auto; font-family: ui-monospace, Menlo, monospace;
              font-size: .72rem; letter-spacing: .02em;
              color: var(--color-neutral-600); white-space: nowrap; }
.sess-noprint { font-family: inherit; font-style: italic;
                color: var(--color-neutral-500); }
/* How many rows share this machine. Beside the fingerprint rather than in
   its own column, because it is a fact ABOUT the fingerprint. */
.sess-dup { margin-left: 6px; padding: 1px 5px; border-radius: 6px;
            font-family: inherit; font-size: .66rem;
            background: var(--color-neutral-200); color: var(--color-neutral-700); }

body.glass .sess-print { color: rgba(244, 243, 240, .66); }
body.glass .sess-dup { background: rgba(255, 255, 255, .12);
                       color: rgba(244, 243, 240, .8); }

/* Below the break the row stacks anyway; the fingerprint goes with it
   rather than being squeezed into a column that is no longer there. */
@media (max-width: 720px) {
  .sessrow { flex-wrap: wrap; }
  .sess-print { flex: 1 1 100%; order: 5; }
}
.sess-name { flex: 1 1 40%; min-width: 0; border: 1px solid transparent;
             border-radius: 7px; background: transparent; color: inherit;
             font: inherit; font-size: .82rem; padding: 4px 6px; }
.sess-name:hover { border-color: var(--color-neutral-300); }
.sess-name:focus { outline: none; border-color: var(--color-accent, #c8663a);
                   background: var(--color-surface); }
.setwarn { margin: 0 0 var(--space-3); padding: 9px 12px; border-radius: 9px;
           background: var(--color-neutral-100); border: 1px solid var(--color-neutral-300);
           border-left: 3px solid #c8663a; font-size: .84rem;
           color: var(--color-neutral-800); }

@media (max-width: 720px) {
  .sessrow { flex-wrap: wrap; }
  .sess-where, .sess-when { flex: 0 0 auto; }
}

/* ── how an account may be used ────────────────────────────────────────
   Folded for staff, open for anybody who is not: for a vendor every one of
   these controls is load-bearing, and for an employee they are all blank. */
.acct-access { margin: var(--space-3) 0 0; padding: 10px 12px; border-radius: 10px;
               background: var(--color-neutral-100);
               border: 1px solid var(--color-neutral-200); }
.acct-access summary { cursor: pointer; font-size: .8rem; font-weight: 600;
                       color: var(--color-neutral-700); }
.acct-access[open] summary { margin-bottom: var(--space-3); }
.acct-field { display: block; margin-bottom: var(--space-2); }
.acct-field > span { display: block; font-size: .74rem; color: var(--color-neutral-600);
                     margin-bottom: 3px; }
.acct-field .input { width: 100%; }
.acct-pair { display: grid; gap: var(--space-2);
             grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
.acct-known { margin-top: var(--space-2); background: var(--color-surface); }
.acct-devlist { margin-top: var(--space-3); display: grid; gap: 4px; }
.tag-3p { background: #6b4423; color: #fff; }
.tag-svc { background: #5a5550; color: #fff; }
.tag-pending { background: #f3e2d8; color: #8a4526; }
.acct-access summary .tag { margin-left: 8px; font-size: .64rem; }

/* Settings fields that are a label, a control and a note under it. The
   existing .setrow is a switch on a line; these are things you type into. */
.setfield { display: block; margin-bottom: var(--space-3); }
.setfield > span:first-child { display: block; font-size: .82rem; font-weight: 600;
                               color: var(--color-neutral-800); margin-bottom: 5px; }
.setfield textarea.input { width: 100%; resize: vertical; font: inherit;
                           font-size: .85rem; line-height: 1.5; }
.setfield .input { width: 100%; }
.setfield .input:disabled { opacity: .6; cursor: not-allowed; }
.setfield .ov-sub { margin-top: 4px; }
.setfield .narrow { width: 90px; }
.setpair { display: grid; gap: var(--space-3);
           grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }

/* ── what the draft could not supply ───────────────────────────────────
   Sits under the paper rather than beside it: the letter is the thing being
   read, and a side rail would either narrow the page or cover it. */
.docgaps { max-width: 8.5in; margin: var(--space-4) auto 0;
           padding: var(--space-3) var(--space-4); border-radius: 12px;
           background: var(--color-surface);
           border: 1px solid var(--color-neutral-300);
           border-left: 3px solid #6b4423; }
.docgaps[hidden] { display: none; }
.docgaps-head { margin-bottom: var(--space-3); }
.docgaps-head strong { display: block; font-size: .9rem; color: var(--color-neutral-900); }
.docgaps-head span { display: block; margin-top: 2px; font-size: .76rem;
                     color: var(--color-neutral-600); }

.docgap { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
/* The placeholder text is the label AND the way to find it on the page. */
.docgap-find { flex: 1 1 40%; min-width: 0; text-align: left; padding: 7px 10px;
               border: 1px solid var(--color-neutral-200); border-radius: 9px;
               background: #fff8dc; color: var(--color-neutral-800);
               font: inherit; font-size: .76rem; line-height: 1.35; cursor: pointer;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.docgap-find:hover { border-color: #6b4423; }
.docgap-in { flex: 1 1 60%; min-width: 0; padding: 7px 10px;
             border: 1px solid var(--color-neutral-300); border-radius: 9px;
             background: var(--color-neutral-100); color: inherit;
             font: inherit; font-size: .8rem; }
.docgap-in:focus { outline: none; border-color: var(--color-accent, #c8663a); }
/* A note is not a gap. It reads differently because it is a different job:
   nothing to type, something to read and take out. */
.docgap-note .docgap-find { background: var(--color-neutral-100);
                            color: var(--color-neutral-700); white-space: normal; }
.docgap-n { display: inline-block; margin-left: 7px; padding: 0 6px; border-radius: 8px;
            background: rgba(107,68,35,.14); color: #6b4423; font-size: .68rem;
            font-weight: 700; }

@media (max-width: 720px) {
  .docgap { flex-wrap: wrap; }
  .docgap-find, .docgap-in { flex: 1 1 100%; }
}

/* ── the bill ledger ───────────────────────────────────────────────────
   A column of money, so it is aligned and tabular. Amounts right-aligned
   against each other rather than left-aligned after a name, because the only
   reason to print a column is to be able to run an eye down it. */
.ev-ledger { border-left: 3px solid var(--color-neutral-300); }
/* minmax(0, 1fr), not the implicit `auto`: a grid item's default min-width is
   auto, so the column sized itself to the row's content and the rows ran
   355px wide inside a 301px card. Nothing looked broken — the page simply
   widened, and the browser zoomed the whole screen out to 393px to fit it,
   so every card on Case value rendered slightly small on a phone rather
   than obviously wrong. */
.ev-led-rows { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; }
.ev-led-row { display: flex; align-items: stretch; gap: 6px; }
.ev-led-name { flex: 1 1 auto; min-width: 0; border: 1px solid var(--color-neutral-300);
               border-radius: 9px; background: var(--color-neutral-100); color: inherit;
               font: inherit; font-size: .84rem; padding: 8px 10px; }
.ev-led-amt { display: flex; align-items: center; width: 140px; flex: none;
              border: 1px solid var(--color-neutral-300); border-radius: 9px;
              background: var(--color-neutral-100); overflow: hidden; }
.ev-led-amt input { width: 100%; border: 0; background: transparent; color: inherit;
                    font: inherit; font-size: .84rem; padding: 8px 10px 8px 2px;
                    text-align: right; font-variant-numeric: tabular-nums; }
.ev-led-name:focus, .ev-led-amt input:focus { outline: none; }
.ev-led-name:focus, .ev-led-amt:focus-within { border-color: var(--color-accent, #c8663a); }
.ev-led-drop { flex: none; width: 34px; border: 1px solid transparent; border-radius: 9px;
               background: transparent; color: var(--color-neutral-500); cursor: pointer;
               font-size: 1.05rem; line-height: 1; }
.ev-led-drop:hover { background: var(--color-neutral-200); color: var(--color-neutral-800); }

@media (max-width: 640px) {
  /* On a phone the provider's name is what you need to read and the amount
     is four or five digits. 140px of right-aligned space for "1842" was
     taken straight out of the name, which truncated to "Southwest Amb". */
  .ev-led-amt { width: 108px; }
  .ev-led-amt input { padding-left: 0; }
}

.ev-led-total { display: flex; align-items: baseline; margin-top: 9px;
                padding-top: 9px; border-top: 1px solid var(--color-neutral-300);
                font-size: .82rem; color: var(--color-neutral-600); }
.ev-led-sum { margin-left: auto; font-size: 1rem; font-weight: 600;
              color: var(--color-neutral-900); font-variant-numeric: tabular-nums;
              /* Line the total up with the column above it, not with the edge. */
              width: 140px; text-align: right; padding-right: 10px; }
.ev-led-actions { justify-content: flex-start; margin-top: var(--space-3); }
.ev-led-how { margin: 8px 0 10px; padding-left: 20px; }
.ev-led-how li { margin: 3px 0; line-height: 1.45; }

/* Offered where a pasted document turned out to contain a column. */
.ev-led-offer { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
                margin: 0 0 var(--space-3); padding: 9px 12px; border-radius: 9px;
                background: var(--color-neutral-100);
                border: 1px solid var(--color-neutral-300);
                font-size: .8rem; color: var(--color-neutral-700); }
.ev-led-offer .btn { margin-left: auto; }

/* The composed draft, shown before it becomes a file in the folder. */
.ev-draft { margin-top: var(--space-5); border-left: 3px solid #6b4423; }
.ev-draft-gaps { margin: 0 0 var(--space-3); padding-left: 18px; }
.ev-draft-gaps li { margin: 4px 0; font-size: .78rem; line-height: 1.45;
                    color: var(--color-neutral-700); }
.ev-draft-text { max-height: 460px; overflow: auto; margin: 0;
                 padding: var(--space-3); border-radius: 9px;
                 background: var(--color-neutral-100);
                 border: 1px solid var(--color-neutral-300);
                 font-family: ui-monospace, Menlo, monospace; font-size: .74rem;
                 line-height: 1.55; white-space: pre-wrap; color: var(--color-neutral-800); }
.ev-draft .ev-actions { justify-content: flex-end; gap: var(--space-2); }

/* ── the firm's own history ────────────────────────────────────────────
   Set apart from the two methods above it because it is a different kind of
   thing: those are heuristics, this is evidence. */
.ev-history { border-left: 3px solid #3f8a5c; }
.ev-history-quiet { border-left-color: var(--color-neutral-300); }
.ev-history-why { margin: 0; font-size: .84rem; line-height: 1.5;
                  color: var(--color-neutral-700); }
.ev-history-caveat { margin: var(--space-3) 0 0; font-size: .74rem; line-height: 1.45;
                     color: var(--color-neutral-500); }
.ev-sub-head { margin: var(--space-3) 0 6px; font-size: .74rem; text-transform: uppercase;
               letter-spacing: .05em; color: var(--color-neutral-500); }
.ev-nearest { margin-top: var(--space-2); }
/* Openable. A range whose sources cannot be read is a number taken on faith. */
.ev-near { display: flex; align-items: baseline; gap: 10px; width: 100%;
           padding: 7px 10px; margin-bottom: 4px; text-align: left;
           border: 1px solid var(--color-neutral-200); border-radius: 9px;
           background: var(--color-neutral-100); color: inherit; font: inherit;
           font-size: .8rem; cursor: pointer; }
.ev-near:hover { border-color: var(--color-neutral-400); }
.ev-near-sim { font-variant-numeric: tabular-nums; color: var(--color-neutral-500);
               min-width: 38px; }
.ev-near-what { color: var(--color-neutral-700); }
.ev-near-out { margin-left: auto; font-weight: 600; font-variant-numeric: tabular-nums; }

.ev-before { border-left: 3px solid #c8663a; }
.ev-before-none { margin: var(--space-3) 0 0; padding: 9px 11px; border-radius: 9px;
                  background: var(--color-neutral-100);
                  border: 1px solid var(--color-neutral-300);
                  font-size: .78rem; line-height: 1.45; color: var(--color-neutral-700); }
.ev-delta { display: flex; align-items: baseline; gap: 12px; margin-top: var(--space-3);
            padding: 10px 12px; border-radius: 9px; background: var(--color-neutral-100);
            border: 1px solid var(--color-neutral-300); }
.ev-delta-n { font-size: 1.35rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.ev-delta-flat .ev-delta-n { color: #8a4526; }
.ev-delta-why { font-size: .78rem; line-height: 1.45; color: var(--color-neutral-700); }

/* What the file is not yet claiming. Open by default: a collapsed list of
   things worth money is a list nobody reads. */
.ev-checklist { margin: 0 0 var(--space-3); padding: 10px 12px; border-radius: 9px;
                background: var(--color-neutral-100);
                border: 1px solid var(--color-neutral-300);
                border-left: 3px solid #6b4423; }
.ev-checklist summary { cursor: pointer; font-size: .82rem; font-weight: 600;
                        color: var(--color-neutral-800); }
.ev-checklist-n { display: inline-block; margin-left: 6px; padding: 1px 7px;
                  border-radius: 9px; background: #6b4423; color: #fff;
                  font-size: .68rem; font-weight: 700; }
.ev-checklist ul { margin: 9px 0 0; padding-left: 18px; }
.ev-checklist li { margin: 6px 0; font-size: .78rem; line-height: 1.5;
                   color: var(--color-neutral-700); }

/* ── the demand card in the search bar ─────────────────────────────────
   Its own colour because it is its own kind of thing: every other preview
   card opens a screen, and this one is the start of a document that goes out
   of the building with the firm's name on it. Brown rather than the shared
   orange so it is recognisable at a glance in a list. */
.peek-kind.kind-demand { background: #6b4423; }
.peek-demand { border-color: rgba(107, 68, 35, .55); }

.peek-facts { display: grid; gap: 3px; margin-top: 8px; }
.peek-fact-row { display: flex; align-items: baseline; gap: 10px; font-size: .82rem; }
.pf-label { color: var(--color-neutral-500); min-width: 106px; }
.pf-value { color: var(--color-neutral-800); font-weight: 600; }
/* Said quietly, but said. A low-confidence reading that looks like the others
   is worse than no reading at all. */
.pf-low { margin-left: auto; font-size: .66rem; text-transform: uppercase;
          letter-spacing: .06em; color: #8a4526; background: #f3e2d8;
          padding: 1px 6px; border-radius: 5px; }
.peek-note, .peek-empty { margin: 9px 0 0; font-size: .74rem; line-height: 1.45;
                          color: var(--color-neutral-500); }

.ev-empty { max-width: 620px; }
.ev-empty h2 { margin: 0 0 4px; font-size: 1.2rem; }
.ev-empty p { margin: 0 0 var(--space-4); color: var(--color-neutral-600); font-size: .9rem; }
.ev-picklist { display: grid; gap: 6px; }
.ev-pick { display: flex; align-items: baseline; gap: var(--space-3); width: 100%;
           text-align: left; padding: 10px 12px; border-radius: 10px;
           border: 1px solid var(--color-neutral-200); background: var(--color-surface);
           color: inherit; font: inherit; cursor: pointer; }
.ev-pick:hover { border-color: var(--color-accent, #c8663a); }
.ev-pick-case { margin-left: auto; color: var(--color-neutral-500); font-size: .8rem;
                font-variant-numeric: tabular-nums; }

@media (max-width: 860px) {
  .evaluate { padding: var(--space-4) var(--space-3) var(--space-7); }
  .ev-pair { grid-template-columns: 1fr; }
}

/* ── the fog, baked ─────────────────────────────────────────────────────

   `backdrop-filter: blur(22px)` asks the compositor to re-sample and blur
   whatever is behind a panel. Behind a photograph that is computed once and
   kept, and costs nothing. Behind a FILM it is recomputed for every frame,
   for every panel on screen, at the display's pixel ratio — thirty-five
   rules' worth of blur over a picture that moves twenty-four times a
   second, which is what the machine was spending its afternoon on.

   The camera in these films does not move. Blurring is averaging, and the
   average of a river is a river-coloured smear that sits still, so the blur
   is computed once by scripts/bake-fog.js and shipped as a 96-pixel picture
   of about five kilobytes. Stretching it across the window IS the blur —
   interpolation between distant samples is what a blur is — and the browser
   was going to draw a background there anyway.

   Each panel below names its own tint, over the fog, in one shorthand. The
   tint has to be repeated because a background-color paints BEHIND a
   background-image: setting the fog alone would hide the panel's colour
   rather than sit under it.

   `background-attachment: fixed` is what keeps the fog in register with the
   film — the same trick .corkboard::after already uses to catch the wall's
   light. It is anchored to the viewport, so a panel that moves reveals a
   different part of it, which is what a window over a landscape does.

   Only under `has-film`. With a still photograph the live blur is free and
   sharper than a bake, so nothing here applies.

   Last in the file, and every selector carries `.glass` as well. Both are
   about being heard: a `background:` shorthand resets background-image, and
   several of these panels declare one later on at the same weight — so the
   fog was set and then wiped, computed styles said `none`, and the panels
   went flat with nothing behind them. Three classes and the last word. */
body.glass.has-film {
  --fog: var(--fog-img, none);
}
body.glass.has-film .home2-bar,
body.glass.has-film .sidebar,
body.glass.has-film .bigsearch2 textarea,
body.glass.has-film .acbox2,
body.glass.has-film .histmenu,
body.glass.has-film .peek-card,
body.glass.has-film .panel,
body.glass.has-film .ivpane,
body.glass.has-film .ivindex,
body.glass.has-film .cfpane,
body.glass.has-film .minichip,
body.glass.has-film .dialog {
  -webkit-backdrop-filter: none !important;
          backdrop-filter: none !important;
  background-attachment: fixed;
  background-size: cover;
  background-position: center;
}

body.glass.has-film .home2-bar,
body.glass.has-film .sidebar {
  /* Grey Nickel, matching the background-COLOUR these two carry. It has to
     be said twice, and that is the trap: under a film these bars are not
     painted by their background-color at all — this image is drawn over it,
     so changing only the colour changes nothing visible. It cost an hour of
     measuring the wrong property to find that out. */
  background-image: linear-gradient(rgba(195,195,189,.80), rgba(195,195,189,.80)), var(--fog);
}

/* ...but not on a screen that has hidden the film. The fog is a still of
   the picture behind the bar, and there is no picture behind it here — the
   ground is flat. Painting a blurred river across the rail of a screen that
   deliberately has no river is the same mistake as leaving the film
   decoding: doing the work for something nobody can see, and in this case
   the work is also visibly wrong. The background-color underneath is the
   answer, which is what it was always meant to be. */
body.glass.has-film.film-off .home2-bar,
body.glass.has-film.film-off .sidebar { background-image: none; }
body.glass.has-film .bigsearch2 textarea {
  background-image: linear-gradient(rgba(28,26,23,.62), rgba(28,26,23,.62)), var(--fog);
}
body.glass.has-film .acbox2,
body.glass.has-film .histmenu {
  background-image: linear-gradient(rgba(52,50,46,.90), rgba(52,50,46,.90)), var(--fog);
}
body.glass.has-film .peek-card,
body.glass.has-film .panel,
body.glass.has-film .dialog {
  background-image: linear-gradient(rgba(52,50,46,.86), rgba(52,50,46,.86)), var(--fog);
}
/* The cards are nearly opaque already, so the fog under them is doing very
   little — but it is the difference between an edge that reads as glass and
   one that reads as a rectangle stuck on. */
body.glass.has-film .ivpane,
body.glass.has-film .ivindex,
body.glass.has-film .cfpane,
body.glass.has-film .minifolder,
body.glass.has-film .minichip {
  background-image: linear-gradient(rgba(26,24,22,.90), rgba(26,24,22,.90)), var(--fog);
}
/* The ask sits over the chips it is asking about, so it is more solid than
   they are — a question you can see the room through is one people read
   twice. */
body.glass.has-film .miniask {
  background-image: linear-gradient(rgba(26,24,22,.95), rgba(26,24,22,.95)), var(--fog);
}

/* ── Dashboard: the same palette the other way up ────────────────────────

   Reminders puts Grey Nickel panels on a black ground. This is the same six
   colours inverted — Grey Nickel IS the ground here, the panels are Grey
   Pepper, and everything is written in Black Wash. A second reading of one
   palette rather than a second palette.

   ── the one thing that constrains the whole screen ───────────────────────

   Grey Pepper is a narrow surface to write on. Measured against #82827d:

     #0a0c0d  Black Wash     5.08   the only one that clears AA
     #212527  Washed Black   4.00   large text only
     #4e4f4d  Black Oak      2.13   fails
     #adada7  Cool Granite   1.71   fails
     #c3c3bd  Grey Nickel    2.18   fails

   So every word on a card is Black Wash, and the hierarchy is carried by
   SIZE AND WEIGHT instead of by lighter greys. That is the right way round
   anyway — dropping contrast to say "less important" makes a thing harder
   to read rather than easier to skip — but here it is not a preference,
   because there is no second legible ink on this surface.

   The lighter greys still do work; they just do not do it as text. Cool
   Granite is the groove a bar sits in, Black Oak is a hairline on the
   ground where it measures 4.65, Washed Black draws the card edges.
   ===================================================================== */
.dbpage {
  --db-ground: #c3c3bd;   /* Grey Nickel   — the page itself */
  --db-panel:  #82827d;   /* Grey Pepper   — the cards */
  --db-ink:    #0a0c0d;   /* Black Wash    — every word on a card */
  --db-edge:   #212527;   /* Washed Black  — card edges */
  --db-rule:   #4e4f4d;   /* Black Oak     — hairlines, on the ground only */
  --db-groove: #adada7;   /* Cool Granite  — the track a bar runs in */
  color: var(--db-ink);
  max-width: 1100px;
}

/* The glass theme writes light type on every screen and this one is light
   ground, so the headings have to be taken back explicitly. Two classes
   beats `body.glass h2`. */
.dbpage h2,
.dbpage .section-title { color: var(--db-ink); }
.dbpage .why {
  color: var(--db-edge); max-width: 62ch;
  margin: -2px 0 var(--space-5);
}

/* A row, not a wrapping grid.

   The five are a sequence — a case moves left to right and ends at
   Settlement — and the arrows between them say so. A grid that wraps would
   put an arrow at the end of a line pointing off the edge at the card
   below, which is the opposite of what it means. So the track stays one
   line and scrolls sideways when there is not room, which keeps the
   progression intact on a phone instead of quietly rearranging it. */
.dbgrid {
  display: flex; align-items: stretch; gap: 0;
  overflow-x: auto; padding-bottom: 4px;
  scrollbar-width: thin;
}
.dbgrid > .dbcard { flex: 1 1 0; min-width: 168px; }

.dbarrow {
  flex: 0 0 auto; align-self: center;
  display: flex; padding: 0 8px;
}
.dbarrow svg {
  width: 22px; height: 22px; fill: none;
  stroke: var(--db-rule);            /* Black Oak — 4.65 on the ground */
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
@media (max-width: 620px) { .dbarrow { padding: 0 4px; } .dbarrow svg { width: 16px; } }

/* A card is a button: pressing it lists those clients. */
.dbcard {
  display: flex; flex-direction: column; align-items: stretch; gap: 7px;
  padding: 14px 15px 15px;
  border-radius: 13px;
  border: 1px solid var(--db-edge);
  background: var(--db-panel);
  color: var(--db-ink);
  font: inherit; text-align: left; cursor: pointer;
  box-shadow: 0 6px 18px rgba(10, 12, 13, .20);
  transition: transform .14s ease, box-shadow .14s ease, background .14s ease;
}
.dbcard:hover {
  background: #8d8d88;                 /* Grey Pepper, lifted a shade */
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(10, 12, 13, .28);
}
.dbcard:focus-visible { outline: 2px solid var(--db-ink); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .dbcard { transition: none; }
  .dbcard:hover { transform: none; }
}

.dbcard-name {
  font-size: .74rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .07em;
}
/* The number is the point of the screen, so it gets the size. */
.dbcard-n {
  font-family: var(--font-heading); font-size: 2.5rem; line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}

/* Scaled against the fullest stage rather than the caseload — five bars all
   under a fifth of their track say nothing, and the question is where the
   weight sits, which is a comparison between them. */
.dbbar {
  display: block; height: 5px; border-radius: 3px;
  background: var(--db-groove); overflow: hidden;
}
.dbbar-fill {
  display: block; height: 100%; border-radius: 3px;
  background: var(--db-ink);
  transition: width .5s cubic-bezier(.2,.7,.2,1);
}
@media (prefers-reduced-motion: reduce) { .dbbar-fill { transition: none; } }

.dbcard-blurb { font-size: .76rem; line-height: 1.4; }
/* Which statuses this card counted. Small and spaced rather than faint —
   see the note above on why nothing here goes grey. */
.dbcard-of {
  font-size: .64rem; letter-spacing: .04em; font-weight: 500;
  padding-top: 7px; border-top: 1px solid rgba(10, 12, 13, .22);
}

/* The totals sit on the GROUND, not on a card, which is the other half of
   the contrast answer: Black Oak is 4.65 here and unusable on a panel. */
.dbfoot {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px;
  margin-top: var(--space-5); padding-top: var(--space-3);
  border-top: 1px solid var(--db-rule);
  font-size: .8rem; color: var(--db-edge);
}
.dbfoot b { font-variant-numeric: tabular-nums; color: var(--db-ink); }
.dbfoot i { font-style: normal; color: var(--db-rule); }
.dbfoot-dot { padding: 0 4px; color: var(--db-rule); }
.dbfoot-aside { display: flex; flex-wrap: wrap; align-items: baseline; }
/* A status the screen does not know about. Loud on purpose: a total that
   does not add up is the one thing this screen must never do quietly. */
.dbfoot-odd { color: var(--db-ink); font-weight: 500; }

@media (max-width: 620px) {
  .dbcard-n { font-size: 2rem; }
  .dbgrid > .dbcard { min-width: 148px; }
}

/* ── the light rail ──────────────────────────────────────────────────────

   The sidebar and the search screen's header are Grey Nickel at .72 now
   (see `body.glass .sidebar`). Only the colour behind them moved; the
   opacity is what makes them read as a bar rather than a slab, so it did
   not.

   Everything written on them had to move with it. The rail was drawn for a
   warm dark bar and wrote in #f4f3f0 — measured against the new bar as it
   actually composites over the film, that ink is:

     over a dark part of the picture   2.60
     over a mid part                   2.14
     over the sun                      1.69

   which is worse the brighter the backdrop gets, and the sun is exactly
   where the top of the rail sits on the daylight and river backdrops. Black
   Wash over the same three: 6.78, 8.26, 10.47.

   So the ink is Black Wash throughout and the hierarchy is carried by
   opacity against a DARK ink rather than by a lighter grey — .82 of black
   still measures 5.9 at the worst point, where Black Oak as a flat colour
   would be 2.85 and fail.

   The hover and active fills invert too: white at .10 on a light bar is
   invisible, so they become black at low alpha, which is the same gesture
   the other way up.
   ===================================================================== */
body.glass .sidebar { color: #0a0c0d; }
body.glass .rail-toggle { color: rgba(10, 12, 13, .70); }
body.glass .rail-toggle:hover { color: #0a0c0d; background: rgba(10, 12, 13, .08); }

body.glass .wordmark { color: #0a0c0d; }
body.glass .wordmark small { color: rgba(10, 12, 13, .60); }

body.glass .nav-item,
body.glass .nav-subitem { color: rgba(10, 12, 13, .82); }
body.glass .nav-item:hover,
body.glass .nav-subitem:hover { background: rgba(10, 12, 13, .09); color: #0a0c0d; }
body.glass .nav-item.active,
body.glass .nav-subitem.active { background: rgba(10, 12, 13, .16); color: #0a0c0d; }

/* The chip is the one thing on the rail that was a light fill on a dark
   bar; on a light bar it becomes a dark fill with light type, which keeps
   it the most emphatic thing there rather than the least. */
body.glass .avatar { background: rgba(10, 12, 13, .78); color: #c3c3bd; }
body.glass .userchip-role { color: rgba(10, 12, 13, .62); }

body.glass .sidebar .btn-ghost { color: rgba(10, 12, 13, .82); }
body.glass .sidebar .btn-ghost:hover {
  background: rgba(10, 12, 13, .09); color: #0a0c0d;
}

/* The rail's own edges, which were white hairlines meant to lift a dark bar
   off a dark room. On a light bar they do nothing; a dark hairline does the
   same job. */
body.glass .sidebar {
  border-right-color: rgba(10, 12, 13, .16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .30);
}

/* ── and the header the search screen carries ─────────────────────────── */
body.glass .home2-bar {
  border-bottom-color: rgba(10, 12, 13, .16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .30);
}
body.glass .home2-brand,
body.glass .home2-who { color: #0a0c0d; }
body.glass .home2-meta { color: rgba(10, 12, 13, .66); }
body.glass .home2-bar .home2-btn,
body.glass .home2-bar .fan-btn,
body.glass .home2-bar .histbtn { color: #0a0c0d; }
body.glass .home2-bar .home2-btn:hover,
body.glass .home2-bar .fan-btn:hover,
body.glass .home2-bar .histbtn:hover { background: rgba(10, 12, 13, .10); }
/* "Submit day" is the one filled button up here and was white-on-dark. */
body.glass .home2-bar .home2-btn:not(.quiet) {
  background: rgba(10, 12, 13, .82); color: #c3c3bd;
}
body.glass .home2-bar .home2-btn:not(.quiet):hover {
  background: rgba(10, 12, 13, .92);
}

/* ═══════════════════════════════════════════════════════════════════════
   CRASH MAP  (public/azmap.js, public/crashes.js)

   A working instrument rather than an illustration. Three rules hold the
   look together and every value below follows from one of them:

     1. The BASEMAP recedes. Counties, roads and place names are grey at
        four different weights and no colour at all, so that the moment a
        crash layer is drawn the coloured dots are the only saturated thing
        on the screen and the eye goes straight to them.

     2. Strokes do not thicken with the zoom. `vector-effect` on every
        stroked path means zooming to a junction reveals more of the map
        rather than a fatter version of the same map.

     3. Detail arrives with the zoom. Suburb names and county labels are
        hidden at the state view — eleven overlapping names across the
        Valley is not information — and appear as `data-zoom` moves through
        far → mid → near. The map is never busier than it is useful.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the page's own palette ───────────────────────────────────────────────
   The glass theme writes light type over a photograph on every screen. This
   one takes it back and states a light ground of its own, for the reason
   given beside `crashes` in FILMLESS (public/app.js): a map is read by
   comparing weights of grey against each other, and a photograph behind it
   changes what those greys are being compared to from one corner to the
   next.

   The ground is Grey Nickel, painted by the body rather than here. What is
   here is everything that sits ON it — and the app's own tokens, taken back
   to light values so that the shared controls inside this page (.input,
   .select, .btn) come back with them rather than each needing a rule.

   The contrast note on .dbpage applies unchanged: on Grey Nickel the light
   greys in the neutral ramp are not legible as text, so the hierarchy here
   is carried by size and weight and every word is close to Black Wash. */
.crashpage {
  --cm-ground: #c3c3bd;    /* Grey Nickel  — the page, painted by FILMLESS */
  --cm-paper:  #fbfbfa;    /* the map itself, the one bright surface */
  --cm-panel:  #ecece9;    /* the filter rail */
  --cm-ink:    #0a0c0d;    /* Black Wash   — every word */
  --cm-sub:    #34383a;    /* the one step down that still measures  */
  --cm-edge:   #212527;    /* Washed Black — panel edges */
  --cm-hair:   #c8c8c3;    /* hairlines inside a panel */

  --color-text:        var(--cm-ink);
  --color-bg:          var(--cm-paper);
  --color-surface:     var(--cm-panel);
  --color-divider:     var(--cm-hair);
  --color-accent:      var(--cm-ink);
  --color-neutral-100: #ffffff;
  --color-neutral-200: #e2e2de;
  --color-neutral-300: #d3d3ce;
  --color-neutral-400: #b3b3ad;
  --color-neutral-500: #6f7173;
  --color-neutral-600: var(--cm-sub);
  --color-neutral-700: #23272a;
  --color-neutral-800: var(--cm-ink);
  --color-neutral-900: var(--cm-ink);

  color: var(--cm-ink);
  display: flex; flex-direction: column; height: 100%; min-height: 0;
}
/* `body.glass` prefixed, and it has to be. The glass theme sets its heading
   colour as `body.glass .section-title`, which is two classes and an element
   — a plain `.crashpage .section-title` is two classes and loses, silently,
   leaving the one word at the top of a light page in off-white. */
body.glass .crashpage h2,
body.glass .crashpage .section-title,
.crashpage h2, .crashpage .section-title { color: var(--cm-ink); }

.crash-head {
  display: flex; align-items: flex-end; gap: var(--space-4);
  flex-wrap: wrap; margin-bottom: var(--space-4);
}
.crash-head h2 { margin: 0; }
.crash-sub {
  font-size: 13px; color: var(--cm-sub);
  max-width: 64ch; margin: 4px 0 0;
}
.crash-head .spacer { flex: 1; }

/* The rail and the map, side by side. The rail is a fixed column because a
   filter that changes width as its contents change is a filter people stop
   trusting; the map takes whatever is left. */
.crash-body {
  display: grid; grid-template-columns: 268px minmax(0, 1fr);
  gap: var(--space-4); align-items: start;
}
@media (max-width: 1080px) {
  .crash-body { grid-template-columns: minmax(0, 1fr); }
}

/* ── the filter rail ─────────────────────────────────────────────────── */
.cf-rail {
  background: var(--cm-panel); border: 1px solid rgba(33, 37, 39, .34);
  border-radius: var(--radius-md);
  /* No padding at the bottom, and the foot supplies its own. A sticky
     element with `bottom: 0` pins to the scrollport's content edge, so a
     padded scroller leaves a strip of that padding BELOW it — nine pixels
     of chips sliding past underneath the buttons that are meant to be
     sitting on the floor. */
  padding: var(--space-3) var(--space-3) 0;
  max-height: calc(100vh - 210px); overflow: auto;
  scrollbar-width: thin;
}
.cf-group { border-top: 1px solid var(--color-divider); padding: var(--space-3) 0; }
.cf-group:first-of-type { border-top: none; padding-top: 2px; }
.cf-group:last-of-type { border-bottom: none; }
.cf-legend {
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--color-neutral-600); margin: 0 0 var(--space-2);
  display: flex; align-items: center; gap: 6px;
}
.cf-legend .cf-n {
  font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--color-neutral-100); background: var(--color-neutral-800);
  border-radius: 999px; padding: 0 6px; font-size: 10px; line-height: 15px;
}
.cf-row { display: flex; gap: var(--space-2); }
.cf-row > * { flex: 1; min-width: 0; }
.cf-rail .input, .cf-rail .select { min-height: 32px; font-size: 13px; padding: 4px 12px; }
.cf-rail .select { padding-right: 28px; background-position: calc(100% - 15px) 13px, calc(100% - 10px) 13px; }
.cf-rail .field > label { font-size: 11px; margin-bottom: 3px; }
.cf-rail .field + .field { margin-top: var(--space-2); }

/* Chips, packed tight. Same control as .chip elsewhere in the app, one size
   down: there are thirty of them here and the rail is 268px wide. */
.cf-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.cf-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px; font: inherit; font-size: 12px;
  border: 1px solid var(--color-divider); background: none;
  color: var(--color-neutral-700); cursor: pointer;
  transition: background .13s, color .13s, border-color .13s;
}
.cf-chip:hover { border-color: var(--color-neutral-500); color: var(--color-text); }
.cf-chip.on {
  background: var(--color-text); border-color: var(--color-text);
  color: var(--color-neutral-100); font-weight: 600;
}
/* A severity chip carries its own colour, because the same five colours are
   the map's key — the chip IS the legend, so there is not a second one to
   keep in step. */
.cf-chip .sw {
  width: 8px; height: 8px; border-radius: 50%; background: var(--sw, currentColor);
  box-shadow: 0 0 0 1px rgba(0,0,0,.14) inset;
}
.cf-chip.on .sw { box-shadow: 0 0 0 1px rgba(255,255,255,.5) inset; }

.cf-foot {
  position: sticky; bottom: 0; background: var(--cm-panel);
  padding: var(--space-2) 0 var(--space-3);
  border-top: 1px solid var(--color-divider); margin-top: var(--space-2);
  display: flex; gap: var(--space-2);
  /* Says there is more above it. The rail is taller than any screen and the
     only other clue is a scrollbar the platform may not draw. */
  box-shadow: 0 -10px 12px -10px rgba(10, 12, 13, .38);
}
.cf-foot .btn { flex: 1; }

/* ── the map panel ───────────────────────────────────────────────────── */
.crash-map {
  position: relative; background: var(--cm-paper);
  border: 1px solid rgba(33, 37, 39, .34); border-radius: var(--radius-md);
  overflow: hidden; min-height: 420px;
  height: calc(100vh - 210px);
}
.azm { position: absolute; inset: 0; }
.azm-svg { width: 100%; height: 100%; display: block; cursor: grab; touch-action: none; }
.azm.dragging .azm-svg { cursor: grabbing; }
.azm-svg:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }

.azm-svg path, .azm-svg circle, .azm-svg line { vector-effect: non-scaling-stroke; }

.azm-county {
  fill: #f0f1ef; stroke: #d8dad6; stroke-width: 1;
  transition: fill .12s;
}
.azm-county:hover { fill: #e7e9e4; }
/* The county the filter names. The map's half of the select in the rail:
   without it, narrowing to Graham and zooming out again leaves no sign of
   which county the count belongs to. */
.azm-county.picked { fill: #e4e8dc; stroke: #9aa291; stroke-width: 1.4; }
.azm-border { fill: none; stroke: #2c2f33; stroke-width: 1.6; stroke-linejoin: round; }

.azm-road { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.azm-road-1 { stroke: #b6bcc3; stroke-width: 2.1; }
.azm-road-2 { stroke: #cdd2d7; stroke-width: 1.2; }

/* Everything below is divided by --azk, the map's current zoom (set on the
   host by azmap.js on every zoom change). Type and dots inside a scaled
   group scale with it, and a place name that doubles with every zoom step
   stops being a label and becomes an obstruction. Dividing by the scale
   cancels the transform exactly, so a 15-unit name is 15 screen pixels at
   1x and at 40x.

   `px` inside an SVG is a user unit, which is what makes this work: the
   division happens in the same space the transform is applied in. */
.azm { --azk: 1; }

.azm-shield {
  fill: #949aa2; font-family: var(--font-heading); font-weight: 700;
  letter-spacing: .04em; text-anchor: middle;
  font-size: calc(16px / var(--azk));
  paint-order: stroke; stroke: #fbfbfa; stroke-width: calc(3.4px / var(--azk));
}
.azm-shield-2 { font-size: calc(13px / var(--azk)); fill: #a9aeb5; }

.azm-cname {
  fill: #a9aeb5; font-family: var(--font-heading); font-weight: 700;
  letter-spacing: .14em; text-anchor: middle; pointer-events: none;
  font-size: calc(17px / var(--azk));
}

/* A zero-length subpath with a round cap: a dot whose size is a STROKE, and
   strokes here are told not to scale. A <circle r="3.4"> would have been the
   obvious thing and grows into a blob. */
.azm-dot {
  fill: none; stroke: #4d525a; stroke-width: 6.4px; stroke-linecap: round;
}
.azm-place text {
  fill: #3a3e44; font-family: var(--font-body); font-weight: 600;
  font-size: calc(16px / var(--azk));
  paint-order: stroke; stroke: #fbfbfa; stroke-width: calc(3.4px / var(--azk));
  /* The gap between the dot and its name, in screen pixels rather than in
     drawing units — otherwise the label drifts off across the county as the
     map is zoomed in. */
  transform: translate(calc(7px / var(--azk)), calc(4.2px / var(--azk)));
}

/* Rule 3. `data-zoom` is written by the map on every zoom change, so the
   whole progressive-disclosure ladder is three selectors rather than a
   traversal of six hundred nodes. */
.azm[data-zoom="far"] .azm-t2 text,
.azm[data-zoom="far"] .azm-t3,
.azm[data-zoom="mid"] .azm-t3 { display: none; }
.azm[data-zoom="near"] .azm-cname { opacity: .45; }
.azm[data-zoom="far"] .azm-shield-2 { display: none; }

/* ── the crashes ─────────────────────────────────────────────────────── */
.azm-pt { stroke: rgba(255,255,255,.85); stroke-width: .9; cursor: pointer; }
.azm-pt.sev-fatal    { fill: #8f3a33; }
.azm-pt.sev-serious  { fill: #b2622d; }
.azm-pt.sev-minor    { fill: #dd9a5a; }
.azm-pt.sev-possible { fill: #8fa073; }
.azm-pt.sev-none     { fill: #9aa0a8; }
.azm-pt.sev-unknown  { fill: #c9ccd1; }
.azm-pt.on { stroke: #1c1e21; stroke-width: 2.2; }

/* ── controls, over the map ──────────────────────────────────────────── */
.azm-nav {
  position: absolute; top: 12px; right: 12px; z-index: 2;
  display: flex; flex-direction: column; gap: 6px;
}
.azm-nav .azb {
  width: 32px; height: 32px; display: grid; place-items: center;
  background: rgba(255,255,255,.94); border: 1px solid var(--color-divider);
  border-radius: 9px; cursor: pointer; color: var(--color-neutral-800);
  box-shadow: var(--shadow-sm); font: inherit; font-size: 15px; line-height: 1;
  transition: background .12s, color .12s;
}
.azm-nav .azb:hover { background: #fff; color: var(--color-text); border-color: var(--color-neutral-500); }
.azm-nav .azb svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.azm-nav .azb-gap { margin-top: 4px; }

.azm-scale {
  position: absolute; left: 14px; bottom: 12px; z-index: 2;
  font-size: 10.5px; color: var(--color-neutral-600); letter-spacing: .02em;
  display: flex; align-items: center; gap: 7px;
}
.azm-scale i { display: block; height: 5px; border: 1px solid var(--color-neutral-600); border-top: none; }

.azm-hint {
  position: absolute; right: 12px; bottom: 12px; z-index: 2;
  font-size: 10.5px; color: var(--color-neutral-500);
  background: rgba(255,255,255,.82); padding: 2px 8px; border-radius: 999px;
}

/* The count, over the top-left of the map. On the map rather than above it
   because it is a statement ABOUT what is drawn, and the two should not be
   able to disagree by the height of a heading. */
.azm-count {
  position: absolute; left: 12px; top: 12px; z-index: 2;
  background: rgba(255,255,255,.94); border: 1px solid var(--color-divider);
  border-radius: 10px; box-shadow: var(--shadow-sm);
  padding: 7px 12px 8px; min-width: 132px;
}
.azm-count b {
  display: block; font-family: var(--font-heading); font-size: 21px;
  line-height: 1.05; letter-spacing: -.02em;
}
.azm-count span { font-size: 11px; color: var(--color-neutral-600); }
.azm-count .azm-trim { color: var(--color-clay-700); font-weight: 600; }

/* ── one crash, opened ───────────────────────────────────────────────── */
.azm-card {
  position: absolute; right: 56px; top: 12px; z-index: 3; width: 262px;
  background: #fff; border: 1px solid var(--color-divider);
  border-radius: var(--radius-md); box-shadow: var(--shadow-lg);
  padding: var(--space-3);
}
.azm-card h4 { font-size: 14px; margin: 0 0 2px; }
.azm-card .ac-when { font-size: 11.5px; color: var(--color-neutral-600); margin-bottom: var(--space-2); }
.azm-card dl { display: grid; grid-template-columns: auto 1fr; gap: 3px var(--space-2); margin: 0; font-size: 12.5px; }
.azm-card dt { color: var(--color-neutral-600); }
.azm-card dd { margin: 0; }
.azm-card .ac-close {
  position: absolute; top: 6px; right: 6px; width: 24px; height: 24px;
  border: none; background: none; cursor: pointer; border-radius: 6px;
  color: var(--color-neutral-600); font-size: 15px; line-height: 1;
}
.azm-card .ac-close:hover { background: var(--color-neutral-200); color: var(--color-text); }

/* ── nothing loaded yet ──────────────────────────────────────────────── */
.crash-none {
  display: flex; gap: var(--space-3); align-items: flex-start;
  border: 1px dashed var(--color-neutral-400); border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4); margin-bottom: var(--space-4);
  background: var(--color-accent-100);
}
.crash-none h4 { font-size: 13.5px; margin: 0 0 3px; }
.crash-none p { font-size: 12.5px; color: var(--color-neutral-700); margin: 0; max-width: 70ch; }
.crash-none code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px;
  background: rgba(255,255,255,.7); padding: 1px 5px; border-radius: 5px;
}

/* ── shared controls, taken back to the light ────────────────────────────
   The glass theme restyles .input, .select and .btn-secondary at two
   classes (`body.glass .input`), which beats one class however local it
   is — so redefining the tokens on .crashpage is not enough for these
   three, and the Clear button was white type on a light panel: present,
   focusable, and invisible.

   Stated at the same specificity plus one, and no higher. Written out
   rather than reached for with !important, because the next person to
   restyle a control globally should still be able to. */
body.glass .crashpage .input,
body.glass .crashpage .select,
body.glass .crashpage textarea.input {
  background: var(--color-neutral-100);
  border-color: rgba(33, 37, 39, .28);
  color: var(--cm-ink); caret-color: var(--cm-ink);
  box-shadow: none;
}
body.glass .crashpage .input::placeholder { color: #7c7f81; }
body.glass .crashpage .input:focus-visible,
body.glass .crashpage .select:focus-visible {
  border-color: var(--cm-ink); background: var(--color-neutral-100);
}
/* The `background` shorthand above resets the arrow gradients, exactly as
   the note on `body.glass .select` says. Same fix, same reason. */
body.glass .crashpage .select {
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  background-color: var(--color-neutral-100);
}
body.glass .crashpage .btn-secondary {
  border-color: rgba(33, 37, 39, .30); color: var(--cm-ink);
  background: var(--color-neutral-100);
}
body.glass .crashpage .btn-secondary:hover:not(:disabled) {
  background: #efefec; border-color: var(--cm-ink);
}

/* ═══════════════════════════════════════════════════════════════════════
   DATA MIGRATION  (public/migration.js)

   A staging area, and it should read as one. Everything on this screen is
   provisional — files that have arrived and been filed nowhere — so the
   drop zone is drawn as an open, dashed outline rather than a solid panel,
   and the list below it is a plain table rather than the case-file rows
   used where things have a home.

   It follows the glass theme like every other ordinary screen, unlike the
   crash map, which takes a light ground because a map is a paper document.
   This is a list of files, and a list of files belongs in the same room as
   the rest of the application.
   ═══════════════════════════════════════════════════════════════════════ */

.migpage { max-width: 1080px; }
.migsec {
  font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 62%, transparent);
  margin: var(--space-6) 0 var(--space-3);
}

/* ── the drop zone ───────────────────────────────────────────────────── */
.migdrop {
  /* Dashed and visible. At a quarter opacity over the photograph the dashes
     disappear and the zone reads as an ordinary panel — which is exactly
     the thing a drop target must not read as. */
  border: 2px dashed color-mix(in srgb, var(--color-text) 42%, transparent);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-6) var(--space-4);
  text-align: center; margin: var(--space-4) 0;
  background: color-mix(in srgb, var(--color-surface) 55%, transparent);
  transition: border-color .14s, background .14s;
}
.migdrop h3 { font-size: 17px; margin: 0 0 4px; }
.migdrop p { font-size: 13.5px; margin: 0 auto; max-width: 62ch; }
.migdrop .hint { margin-top: var(--space-3); }
.migdrop-ico {
  display: inline-grid; place-items: center; width: 42px; height: 42px;
  border-radius: 12px; margin-bottom: var(--space-2);
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
}
.migdrop-ico svg {
  width: 22px; height: 22px; fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
/* Carrying files, and over the target. A colour change alone would say
   nothing to anybody dragging with the pointer in the way, so the border
   goes solid too — the shape of the outline changes, not only its hue. */
.migdrop.is-over {
  border-style: solid;
  border-color: var(--color-accent-2-500);
  background: color-mix(in srgb, var(--color-accent-2-500) 12%, transparent);
}
/* No scanner. Not a disabled drop zone — there is nothing to drop onto. */
.migdrop.is-off {
  border-color: color-mix(in srgb, var(--color-text) 16%, transparent);
  text-align: left; padding: var(--space-4) var(--space-6);
}
/* The centred-block rule above is for the invitation, not for the refusal.
   `margin: 0 auto` on a 62ch paragraph inside a left-aligned panel indents
   it away from the heading it belongs to. */
.migdrop.is-off p { margin: 0; max-width: 74ch; }
.migdrop.is-off h3 { color: var(--color-clay-700); }
body.glass .migdrop.is-off h3 { color: #ffb4a8; }
.migdrop.is-off code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
  padding: 1px 5px; border-radius: 5px;
  background: color-mix(in srgb, var(--color-text) 10%, transparent);
}
/* Visually hidden, not display:none — a hidden input cannot be reached by
   the label that opens it in some assistive tooling, and this is the only
   way in for anybody who does not drag. */
.migpick {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* Underlined, because it is the only way in for anybody who does not drag
   and it is sitting in the middle of a sentence. `.rowlink` colours itself
   with the accent, and under the glass theme the accent IS very nearly the
   body colour — so colour alone leaves it looking like the words either
   side of it. */
.migdrop label.rowlink { cursor: pointer; text-decoration: underline; }

/* ── the queue ───────────────────────────────────────────────────────── */
.migq { margin: var(--space-4) 0; padding: var(--space-3) var(--space-4) var(--space-2); }
.migq-head {
  display: flex; align-items: baseline; gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.migq-title { font-size: 14px; margin: 0; }
.migq-count { font-size: 12px; color: var(--color-neutral-600); flex: 1; }

.migjob {
  display: grid; grid-template-columns: minmax(0, 1fr) 74px 132px;
  gap: var(--space-3); align-items: center;
  padding: 7px 0; border-top: 1px solid var(--color-divider);
  font-size: 13px;
}
.migq-list > .migjob:first-child { border-top: none; }
.migjob-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.migjob-size { color: var(--color-neutral-600); font-variant-numeric: tabular-nums; }
.migjob-state {
  display: flex; align-items: center; gap: var(--space-2); justify-content: flex-end;
  font-variant-numeric: tabular-nums; color: var(--color-neutral-700);
}
.migjob.is-done .migjob-state { color: #2f6b46; }
.migjob.is-failed .migjob-state { color: #b03636; font-weight: 600; }
body.glass .migjob.is-done .migjob-state { color: #a9d8b8; }
body.glass .migjob.is-failed .migjob-state { color: #ffb4a8; }

/* The refusal goes UNDER the file it refused, spanning the row, rather than
   in a toast. A toast is gone in four seconds and this is the only place
   the reason exists — and on a migration of four hundred files, "which one
   and why" is the entire question. */
.migjob-why {
  grid-column: 1 / -1; margin: 2px 0 4px; font-size: 12px;
  color: #b03636; max-width: 78ch;
}
body.glass .migjob-why { color: #ffb4a8; }

.migbar {
  display: block; width: 62px; height: 5px; border-radius: 999px; overflow: hidden;
  background: color-mix(in srgb, var(--color-text) 14%, transparent);
}
.migbar i {
  display: block; height: 100%; background: var(--color-accent-2-500);
  transition: width .18s linear;
}
/* The browser would not say how far it had got — see api.js. The bar fills
   the track and slides, so it reads as "working" rather than as a number
   nobody measured. */
.migbar-idk i {
  background: linear-gradient(90deg,
    transparent 0%, var(--color-accent-2-500) 45%, transparent 90%);
  animation: migslide 1.1s linear infinite;
}
@keyframes migslide { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .migbar-idk i { animation: none; background: var(--color-accent-2-500); opacity: .5; }
}

/* ── what arrived ────────────────────────────────────────────────────── */
.migtable-wrap {
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  overflow: auto; background: var(--color-surface);
}
.migtable td { vertical-align: middle; }
.migtable .migname a { color: inherit; font-weight: 600; text-decoration: none; }
.migtable .migname a:hover { text-decoration: underline; }
.migtable .migmime {
  font-size: 11.5px; letter-spacing: .04em; color: var(--color-neutral-600);
}
.migtable tr:hover .filerow-drop, .migtable .filerow-drop:focus-visible { opacity: 1; }

@media (max-width: 760px) {
  .migjob { grid-template-columns: minmax(0, 1fr) 110px; }
  .migjob-size { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   THE PLAIN BUILD

   No photographs, no room video, no audio. Set from /api/config at boot —
   see PLAIN in server/routes/api.js — and applied to <html> rather than
   <body> because the sign-in screen swaps body classes and this must
   outlive that.

   It is not a new theme. `body.glass` is a treatment for type sitting over
   a photograph, and with no photograph it is light text on nothing; so the
   plain build simply does not add that class, and the app falls back to the
   light theme every token in :root already describes. What is left here is
   the handful of places that paint a ground or a photograph directly.

   The reason is licensing, not taste. The images and audio are placeholders
   that have never been cleared for redistribution, and `public/` is served
   statically — on a public host an unreferenced JPG is still a published
   work at a guessable URL. So the deployed build does not reference them
   and scripts/deploy.sh does not ship them. Locally, without the flag,
   everything is exactly as it was.
   ═══════════════════════════════════════════════════════════════════════ */

html.plain, html.plain body { background: #ffffff; }

/* The room, the light, the weather and the wall — every layer that exists
   to sit behind a photograph. Held down with !important because several of
   them are painted by three-class selectors (`body.glass.lit-room.light-moves`)
   that a single class cannot out-specify, and the point here is that none of
   them can come back. */
html.plain body::before,
html.plain body::after,
html.plain .skylight,
html.plain .leafwind,
html.plain .filmroom,
html.plain .wxfilter { display: none !important; background-image: none !important; }

/* Anything that reaches for a backdrop file by name. */
html.plain .pixel-swap__backdrop,
html.plain .corkboard::after,
html.plain .wsi::after { background-image: none !important; }

/* The sign-in card. Without the room behind it a dark glass panel floating
   on white reads as an error, so it becomes an ordinary card: white, edged,
   and lifted just enough to be a surface. */
html.plain .login-card {
  background: #ffffff;
  border: 1px solid var(--color-divider);
  box-shadow: var(--shadow-md);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
html.plain .login-card .field > label { color: var(--color-neutral-700); }
html.plain .wordmark, html.plain .login-brand { color: var(--color-text); }

/* The one place a plain build should say something rather than silently
   offer less: the Settings controls for the backdrop and the sound. */
.plain-note {
  border: 1px dashed var(--color-neutral-400); border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4); margin: var(--space-3) 0;
  font-size: 13px; color: var(--color-neutral-700); max-width: 68ch;
}
.plain-note b { color: var(--color-text); }
