/* The panel's look (Phase 5 item 50b; the design pass of 2026-09-07 finished it).
 *
 * Every value here is lifted from `docs/design/influencer-panel-prototype.dc.html`,
 * which the design pass makes the authority for looks. One file rather than values
 * eyedropped per screen — which is how a product goes off-brand one screen at a time.
 *
 * Three things the prototype does not decide, and this file does:
 *   - the card's left edge is **urgency**, not the action kind (PHASE5_SPEC §B/§D);
 *     the kind is the icon, and a service gets its own colour family;
 *   - the desktop layout, which the prototype has no artboard for: the same frame,
 *     opened out to a rail plus two panes (see "the desktop" at the bottom);
 *   - the screens the prototype skips — sign-in, the customer sheet, the archive
 *     filters — drawn out of the components above rather than invented.
 */

:root {
  /* --- surfaces ------------------------------------------------------------- */
  --bg: oklch(16% 0.03 285);
  --page: oklch(15% 0.03 285);
  --frame: oklch(23% 0.035 285 / 0.62);
  --frame-ink: oklch(24% 0.035 285);

  /* --- ink ------------------------------------------------------------------ */
  --ink: oklch(96% 0.01 285);
  --ink-strong: oklch(97% 0.01 285);
  --ink-soft: oklch(88% 0.015 285);
  --ink-dim: oklch(76% 0.03 285);
  --ink-mute: oklch(72% 0.03 285);
  --ink-faint: oklch(66% 0.02 285);
  --ink-inverse: oklch(22% 0.04 285);

  /* --- edges and fills ------------------------------------------------------ */
  --line: oklch(100% 0 0 / 0.12);
  --line-soft: oklch(100% 0 0 / 0.07);
  --line-hair: oklch(100% 0 0 / 0.16);
  --raise: oklch(100% 0 0 / 0.06);
  --raise-2: oklch(100% 0 0 / 0.08);
  --raise-3: oklch(100% 0 0 / 0.12);
  --raise-hover: oklch(100% 0 0 / 0.14);

  /* The money mint. AVAILABLE glows in it; so does the fan-message counter. */
  --on: oklch(84% 0.19 165);
  --on-ink: oklch(88% 0.16 165);
  --accent: oklch(80% 0.13 200);
  --focus: oklch(82% 0.16 200);
  --cyan: oklch(84% 0.17 200);
  /* Team messages. Deliberately far from the mint: the brief wants the two CHAT
   * counters in two colours so a creator tells fan traffic from internal at a glance. */
  --team: oklch(80% 0.17 300);
  --warn: oklch(72% 0.19 25);
  --warn-ink: oklch(84% 0.13 25);
  --live: oklch(84% 0.19 145);

  /* The one gradient in the product: a primary action, and the progress bar. */
  --go-grad: linear-gradient(90deg, oklch(84% 0.18 165), oklch(84% 0.17 200));
  --go-glow: 0 0 26px -6px oklch(84% 0.18 185 / 0.85);

  --mono: "Inconsolata", ui-monospace, SFMono-Regular, Menlo, monospace;
  --sans: Helvetica, "Helvetica Neue", Arial, sans-serif;

  /* Radii, straight off the prototype's shapes. */
  --r-card: 16px;
  --r-tile: 15px;
  --r-control: 13px;
  --r-chip: 9px;
}

* { box-sizing: border-box; -webkit-font-smoothing: antialiased; }
/* Everything the browser draws itself — a `<select>`'s popup list, the scrollbars, the
   focus rings, a file dialog's button — is drawn light unless the page says which side
   it is on. Without this the creator picker's list came out as our light text on the
   system's white, which is to say invisible. */
:root { color-scheme: dark; }
html, body { margin: 0; padding: 0; background: var(--bg); }

body {
  font: 15px/1.45 var(--sans);
  color: var(--ink);
  min-height: 100dvh;
  height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: min(5vh, 44px) min(4vw, 16px);
  overflow: hidden;
  background: var(--page);
}

/* --- the ambient light --------------------------------------------------------
 * Three drifting blobs behind the glass. They are what makes the frame read as
 * glass at all: `backdrop-filter` over a flat colour is invisible. */

.glow { position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.glow i { position: absolute; border-radius: 50%; display: block; }
.glow .g1 {
  top: -14%; left: -12%; width: 62%; height: 62%;
  background: oklch(58% 0.24 300); filter: blur(110px); opacity: 0.5;
  animation: drift 18s ease-in-out infinite;
}
.glow .g2 {
  bottom: -16%; right: -10%; width: 58%; height: 58%;
  background: oklch(64% 0.2 200); filter: blur(120px); opacity: 0.45;
  animation: drift 22s ease-in-out infinite reverse;
}
.glow .g3 {
  top: 34%; right: 22%; width: 34%; height: 34%;
  background: oklch(66% 0.22 345); filter: blur(120px); opacity: 0.35;
  animation: drift 26s ease-in-out infinite;
}
@keyframes drift {
  0% { transform: translate3d(0, 0, 0) scale(1); }
  50% { transform: translate3d(0, -24px, 0) scale(1.08); }
  100% { transform: translate3d(0, 0, 0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .glow i { animation: none; }
}

/* --- the frame ----------------------------------------------------------------
 * `100dvh` rather than `vh` because a mobile browser's toolbar collapses and `vh`
 * would leave the footer under it. */

.frame {
  position: relative;
  z-index: 1;
  width: min(440px, 100%);
  height: 100%;
  max-height: 100%;
  min-height: min(720px, 100%);
  display: flex;
  flex-direction: column;
  border-radius: min(26px, 6vw);
  overflow: hidden;
  background: var(--frame);
  backdrop-filter: blur(30px) saturate(150%);
  -webkit-backdrop-filter: blur(30px) saturate(150%);
  color: var(--ink);
  box-shadow: 0 0 0 1px oklch(100% 0 0 / 0.12),
              inset 0 1px 0 oklch(100% 0 0 / 0.18),
              0 40px 80px -30px oklch(8% 0.06 285 / 0.9);
}

.rule {
  flex: none;
  height: 1px;
  background: linear-gradient(to right,
    oklch(100% 0 0 / 0) 0%, oklch(100% 0 0 / 0.18) 50%, oklch(100% 0 0 / 0) 100%);
}

.hidden { display: none !important; }
/* A tab that already holds a session (session.js marks the document before the first
   paint) does not show the sign-in form while the page finds out who this is; the form
   comes back only when the session is forgotten. */
html[data-session="held"] #signin { display: none; }

/* The scrollbars. A default light one on a dark glass frame is the loudest thing on
   the screen — louder than the money — so they are thin and made of the same lines
   everything else is drawn with. */
* { scrollbar-width: thin; scrollbar-color: oklch(100% 0 0 / 0.18) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: oklch(100% 0 0 / 0.16); border-radius: 999px;
  border: 2px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: oklch(100% 0 0 / 0.28); background-clip: content-box; }

/* --- the money bar (§A) -------------------------------------------------------- */

.topbar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 10px 18px;
  padding: 20px 22px 16px;
  font-family: var(--mono);
}
.brand { display: none; align-items: baseline; gap: 9px; min-width: 0; }
/* Who you are and where else you may work are one group on the left; the money is the
   group on the right. Two `margin-right: auto`s split the bar in three and left the
   sign-out chip floating in the middle of it. */
.topbar .surfaces { margin-right: auto; }
.topbar .brand + .surfaces { margin-left: 4px; }
/* …and when there is no switcher to split it at — a creator's bar, since item 194 moved
   Password and Sign out into OPTIONS, holds no chip at all and hides — the name itself
   takes the split, or the money sat beside it in the middle of the bar. */
.topbar .brand { margin-right: auto; }
.topbar .brand:has(+ .surfaces:not(.hidden)) { margin-right: 0; }
.brand b {
  font: 600 15px/1 var(--mono); letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-strong);
}
.brand span {
  font: 400 11px/1 var(--mono); letter-spacing: 0.06em; color: var(--ink-mute);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The switcher: the other pages this person may work in, and the way out. In the bar
   because the bar is the one thing every screen has, and beside the name because that
   is what it is about. */
.surfaces { display: flex; align-items: center; gap: 6px; }
.surfaces-list { display: flex; align-items: center; gap: 6px; }
.surfaces .chip { text-decoration: none; height: 30px; }
/* Two classes, because `.icon-button { display: flex }` comes later in this file and a
   single class lost to it — the phone's button showed on every desktop bar (found the
   same day, item 126a's pass). */
.surfaces > .surfaces-toggle { display: none; }
@media (max-width: 1023px) {
  .surfaces { order: 3; flex-basis: 100%; margin-right: 0; justify-content: flex-end; }
}

/* Whose panel this is, when it is not your own. Sits where the money would be loud
   enough to notice, because answering a fan as somebody else is exactly the thing you
   want to have noticed. */
/* 30 px tall, like her bar's chips and money (item 120a, QA F-40): at 42 it made the
   header of every Working-as panel 12 px taller and started the navigation lower. */
.acting {
  display: flex; align-items: center; gap: 9px;
  padding: 3px 3px 3px 13px; border-radius: 999px;
  background: oklch(80% 0.17 300 / 0.14);
  box-shadow: inset 0 0 0 1px oklch(80% 0.17 300 / 0.4);
}
.acting-label {
  font: 400 10px/1 var(--mono); letter-spacing: 0.12em; text-transform: uppercase;
  color: oklch(88% 0.1 300);
}
/* Its own right padding (item 140, QA F-61): the bar's 3px is for the *picker*, which
   has a box of its own, and with one creator the name sat against the border. */
.acting-name {
  font: 600 13px/1 var(--sans); color: var(--ink-strong); padding-right: 10px;
}
.acting-pick {
  width: auto; height: 24px; padding: 0 8px;
  font: 600 13px/1 var(--sans); color: var(--ink-strong);
  background: oklch(100% 0 0 / 0.1); border-radius: 8px;
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.14);
}
/* An admin's list is every creator (item 91): a box narrows it, beside the picker. */
.acting-find {
  width: 9.5em; min-width: 0; height: 24px; padding: 0 10px;
  font: 400 12.5px/1 var(--sans); border-radius: 8px;
}
/* Nobody's money is shown in somebody else's panel, so the labels go with it. */
.topbar.no-money .money, .topbar.no-money .money-divider,
.topbar.no-money .money-note { display: none; }

.money { display: flex; align-items: baseline; gap: 8px; }
.money-label {
  font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-mute);
}
.money-value {
  display: inline-flex; align-items: baseline;
  font-weight: 600; letter-spacing: -0.01em;
  /* Tabular figures, because these numbers animate: proportional digits make the
   * whole line jitter as each one changes width mid-count. */
  font-variant-numeric: tabular-nums;
  font-size: 18px;
}
/* The prototype splits the number: full-size units, small cents. */
.money-value .cents { font-size: 10px; opacity: 0.75; margin-left: 1px; }
.money-value.available { color: var(--on-ink); text-shadow: 0 0 18px oklch(80% 0.2 165 / 0.55); }
.money-value.total { color: oklch(94% 0.01 285); font-weight: 500; }
.money-value.total .cents { opacity: 0.7; }
.money-divider { flex: none; width: 1px; height: 16px; background: var(--line-hair); }
.money-note {
  flex-basis: 100%; text-align: right; font-size: 10px; letter-spacing: 0.04em;
  color: var(--warn-ink);
}

/* --- the middle, and the two panes --------------------------------------------
 * §A: "Zmienia się wyłącznie środkowa część ekranu". On a phone the detail pane
 * covers the list; on a desktop they sit side by side (see "the desktop"). */

.middle { flex: 1 1 auto; min-height: 0; display: flex; }
.panes { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; }
.pane { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
/* The list pane holds a sheet too now — the archive picker over the ADD form. */
.pane-list { position: relative; }
/* The customer sheet is absolute inside the detail pane, so the pane is its frame. */
.pane-detail { position: relative; }
.panes[data-detail="0"] .pane-detail { display: none; }
.panes[data-detail="1"] .pane-list { display: none; }

/* A screen: an optional fixed head, a scrolling body, an optional fixed foot. */
.view { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.view-scroll {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  display: flex; flex-direction: column; gap: 16px;
  padding: 24px 18px 22px;
}
/* A flex column shrinks its children before it scrolls, so a form taller than the
   pane squeezed its own input and button to a sliver (the ADD screen, 2026-09-08: a
   22px "Save to the library"). Controls keep their height; the column scrolls. */
.view-scroll > input, .view-scroll > select, .view-scroll > textarea,
.view-scroll > button.go { flex: none; }
.view-head { flex: none; display: flex; align-items: flex-start; gap: 12px; padding: 16px 18px 12px; }
.view-foot {
  flex: none; display: flex; flex-direction: column; gap: 10px;
  padding: 14px 18px 16px;
  background: oklch(100% 0 0 / 0.05);
  box-shadow: inset 0 1px 0 var(--line);
}
.inset { padding-left: 8px; padding-right: 8px; }

.screen-title {
  font-size: 13px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
}
.screen-sub {
  font-size: 12px; letter-spacing: 0.05em; color: var(--ink-dim); font-family: var(--mono);
}
h2.section {
  margin: 0;
  font: 600 13px/1.2 var(--sans);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink);
}
.detail-label {
  font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase;
  font-family: var(--mono); color: var(--ink-dim);
}
.detail-block { display: flex; flex-direction: column; gap: 9px; }
.placeholder {
  border-radius: 14px; padding: 16px 18px;
  background: var(--raise); box-shadow: inset 0 0 0 1px var(--line);
  color: var(--ink-dim); font: 400 12.5px/1.6 var(--mono);
}
.note-line { font: 400 11.5px/1.6 var(--mono); color: var(--ink-dim); letter-spacing: 0.02em; }
.note-line.err { color: var(--warn-ink); }
.empty-pane {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 24px; text-align: center; color: var(--ink-faint);
  font: 400 12px/1.6 var(--mono); letter-spacing: 0.05em;
}
.empty-pane svg { opacity: 0.5; }

/* --- controls ------------------------------------------------------------------
 * Shared, not admin-only: the shell has a sign-in box too, and leaving these in one
 * page meant the panel's own inputs rendered as browser-default white boxes on a
 * dark frame — visible the moment the shell was opened in a browser. */

input, select, textarea, button.go {
  font: 400 14.5px/1.45 var(--sans);
  color: var(--ink-strong);
  background: var(--raise-2);
  border: 0;
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.14);
  border-radius: var(--r-control);
  padding: 12px 14px;
  width: 100%;
}
input, select { height: 46px; padding: 0 14px; }
/* Said as well as declared: `color-scheme` is what actually fixes the popup, and these
   two are what a browser that ignores it falls back to. A list nobody can read is worse
   than a list that does not match the frame. */
select option, select optgroup {
  background: oklch(22% 0.03 285);
  color: var(--ink);
}
/* A tick box is not a text field: the rule above made it a full-width 46px slab, which
   is what the roles page showed. It keeps the palette and its own size. */
input[type="checkbox"], input[type="radio"] {
  width: 18px; height: 18px; padding: 0; flex: none;
  border-radius: 5px; accent-color: var(--on);
  box-shadow: none; appearance: auto;
}
input[type="radio"] { border-radius: 50%; }
textarea { resize: none; line-height: 1.45; }
input::placeholder, textarea::placeholder { color: var(--ink-faint); }
/* An autofilled field. Chrome paints it `rgb(232,240,254)` and ignores `background`,
   so a password manager filling the panel in turned two fields white on a dark card.
   Painting over it with a huge inset shadow works and looks wrong — a flat slab where
   every other field is translucent glass over the moving background behind it.
   Deferring the transition is the fix that keeps the field: Chrome's colour is applied
   as a transition, so a delay it will never reach leaves ours in place. Only the text
   colour has to be forced, and that one Chrome does let us set. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
  -webkit-text-fill-color: var(--ink-strong);
  caret-color: var(--ink-strong);
  transition: background-color 0s 9999s, color 0s 9999s;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  box-shadow: inset 0 0 0 1px var(--focus);
}
/* A field that cannot go out as it is (Kamil, 2026-09-09: "everything must be
   transparent"). The ring turns warm and the reason sits right under the field — not
   under the button, three screens down, where the first refusal sat and hid. The ring
   outranks focus so it stays while she is fixing it; her next keystroke clears it. */
input.invalid, textarea.invalid, select.invalid,
input.invalid:focus, textarea.invalid:focus, select.invalid:focus {
  box-shadow: inset 0 0 0 1px var(--warn);
}
.drop.invalid { color: var(--warn-ink); box-shadow: inset 0 0 0 1px var(--warn); }
.field-note { font: 400 11.5px/1.5 var(--mono); color: var(--warn-ink); letter-spacing: 0.02em; }
/* A note whose line is held before it has anything to say (item 142): the words arriving
   or leaving move nothing around it. */
.field-note.held { min-height: 3em; }

button:focus-visible, [role="button"]:focus-visible, [role="tab"]:focus-visible,
[role="switch"]:focus-visible, [role="checkbox"]:focus-visible, [role="radio"]:focus-visible,
a:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--focus);
}

/* The primary action: the gradient, and grey when it cannot fire. */
button.go {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  height: 48px; padding: 0 18px;
  font: 600 14px/1 var(--sans); letter-spacing: 0.01em;
  cursor: pointer;
  color: var(--ink-inverse);
  background: var(--go-grad);
  box-shadow: var(--go-glow);
  border-radius: var(--r-control);
}
button.go:hover { filter: brightness(1.06); }
button.go[disabled], button.go.off {
  color: var(--ink-faint); background: oklch(100% 0 0 / 0.07);
  box-shadow: inset 0 0 0 1px var(--line); filter: none; cursor: default;
}
/* The destructive twin. Never gradient — a decline is not the happy path. */
button.decline {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  flex: none; height: 48px; padding: 0 16px;
  border: 0; border-radius: var(--r-control); cursor: pointer;
  font: 600 13.5px/1 var(--sans);
  color: var(--warn-ink);
  background: oklch(70% 0.18 25 / 0.14);
  box-shadow: inset 0 0 0 1px oklch(75% 0.16 25 / 0.4);
}
button.decline:hover { background: oklch(70% 0.18 25 / 0.22); }

.icon-button {
  flex: none; width: 40px; height: 40px; border: 0; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; color: oklch(88% 0.02 285); background: oklch(100% 0 0 / 0.07);
  box-shadow: inset 0 0 0 1px var(--line);
  transition: background 180ms ease, color 180ms ease;
}
.icon-button:hover { background: var(--raise-hover); color: var(--ink); }
.icon-button[disabled] { opacity: 0.35; cursor: default; background: oklch(100% 0 0 / 0.04); }
.icon-button.sm { width: 30px; height: 30px; border-radius: var(--r-chip); color: oklch(82% 0.02 285); background: var(--raise); }

/* --- chips: one of a set, or on/off -------------------------------------------- */

.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 12px;
  border: 0; border-radius: 999px; cursor: pointer;
  font: 400 10.5px/1 var(--mono); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-dim); background: var(--raise);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.1);
  transition: background 140ms, color 140ms;
}
.chip:hover { background: var(--raise-hover); color: var(--ink); }
.chip[aria-pressed="true"] {
  color: var(--ink-strong); background: oklch(100% 0 0 / 0.16);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.18);
}
/* A filter is not a sort, and the two must not read as one group with two things
   picked. A toggle reads as on/off: mint-outlined when on, so the sort pair keeps
   the "one of these" shape. */
.chip.toggle[aria-pressed="true"] {
  color: var(--ink-strong); background: oklch(100% 0 0 / 0.14);
  box-shadow: inset 0 0 0 1px oklch(84% 0.19 145 / 0.6);
}
/* A segmented group: the track holds the chips, one of which is lit. */
.segment {
  display: inline-flex; gap: 3px; padding: 3px; border-radius: 999px;
  background: var(--raise); box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.1);
}
.segment .chip { background: transparent; box-shadow: none; }
/* Item 207: the kind row under the gallery's tabs — the same switch, one step quieter,
   and its own row: pulled up it sat on the tabs' pill (Kamil, 2026-10-07, screenshot). */
.segment.kinds { margin-top: 6px; }
.segment.kinds .chip { font-size: 10.5px; height: 28px; }
.segment .chip:hover { background: oklch(100% 0 0 / 0.08); }
.segment .chip[aria-pressed="true"] { background: oklch(100% 0 0 / 0.16); box-shadow: none; }

/* An on/off switch with a track and a knob — the prototype's TURBO and DELEGATE. */
.switch {
  display: inline-flex; align-items: center; gap: 9px;
  height: 38px; padding: 0 12px 0 14px;
  border: 0; border-radius: 999px; cursor: pointer;
  font: 400 10.5px/1 var(--mono); letter-spacing: 0.1em; text-transform: uppercase;
  color: oklch(80% 0.02 285); background: var(--raise);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.1);
}
.switch:hover { background: var(--raise-hover); }
.switch .track {
  width: 30px; height: 16px; border-radius: 999px; display: flex; align-items: center;
  padding: 2px; background: oklch(100% 0 0 / 0.18);
  transition: background 200ms ease;
}
.switch .knob {
  width: 12px; height: 12px; border-radius: 50%; background: oklch(90% 0.01 285);
  transition: transform 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.switch[aria-checked="true"] { color: var(--ink-strong); background: oklch(100% 0 0 / 0.16); }
.switch[aria-checked="true"] .track { background: var(--on); }
.switch[aria-checked="true"] .knob { background: var(--ink-inverse); transform: translateX(14px); }
.switch.turbo[aria-checked="true"] {
  box-shadow: inset 0 0 0 1px oklch(84% 0.18 185 / 0.6), 0 0 22px -6px oklch(84% 0.18 185 / 0.8);
}
.switch.team[aria-checked="true"] .track { background: var(--team); }
.switch.team[aria-checked="true"] { box-shadow: inset 0 0 0 1px oklch(80% 0.17 300 / 0.5); }
/* The bigger switch on an OPTIONS row: 34×19 with a 14px knob. */
.switch.bare { padding: 0; height: auto; background: none; box-shadow: none; }
.switch.bare .track { width: 34px; height: 19px; padding: 2.5px; }
.switch.bare .knob { width: 14px; height: 14px; }
.switch.bare[aria-checked="true"] .knob { transform: translateX(15px); }
.switch .dot-live {
  width: 8px; height: 8px; border-radius: 50%; background: oklch(70% 0.02 285);
}
.switch[aria-checked="true"] .dot-live {
  background: var(--live); box-shadow: 0 0 10px oklch(84% 0.19 145 / 0.9);
}

/* --- pills: four different questions, four different families ------------------
 * §D forbids mixing signals, so each pill answers one thing and only one:
 *   .pill          — which service a thing came from   (violet, or its own hue)
 *   .pill.spend    — what a conversation is worth      (mint)
 *   .pill.role     — what a colleague may do           (steel blue)
 *   .pill.done/.declined — how something ended         (mint / red)
 */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  font: 400 10px/1 var(--mono); letter-spacing: 0.08em; text-transform: uppercase;
  padding: 4px 7px; border-radius: 6px;
  color: oklch(90% 0.12 285); background: oklch(70% 0.12 285 / 0.18);
}
/* A service that names itself gets its own hue, set on the node. */
.pill.hue { color: oklch(90% 0.12 var(--hue)); background: oklch(80% 0.17 var(--hue) / 0.16); }
.pill.muted { color: var(--ink-mute); background: oklch(100% 0 0 / 0.06); }
.pill.done { color: var(--on-ink); background: oklch(84% 0.19 165 / 0.18); }
.pill.declined { color: var(--warn-ink); background: oklch(70% 0.18 25 / 0.18); }
.pill.waiting { color: oklch(88% 0.02 285); background: oklch(100% 0 0 / 0.1); }
.pill.role { color: oklch(88% 0.1 220); background: oklch(80% 0.14 220 / 0.16); }
/* The spender mark. Its own family again — it answers what a conversation is worth —
   and one colour, as the design draws it: what he earned her, in mint, under the time.
   82e's tiers live on in the TOP SPEND ordering, not in the colour. */
.pill.spend {
  padding: 0; background: none; border-radius: 0;
  font: 600 11.5px/1 var(--mono); letter-spacing: 0; text-transform: none;
  font-variant-numeric: tabular-nums; color: oklch(86% 0.16 165);
}
.pill.spend .unit { margin-left: 3px; font-size: 9.5px; opacity: 0.7; }

.pill-icon { display: inline-flex; padding: 4px 9px 4px 7px; border-radius: 7px;
  font-size: 10px; letter-spacing: 0.12em; }

/* --- TO DO (§B) ---------------------------------------------------------------- */

/* Urgency is the card's edge colour (and, since D-3, its group in the list). §D wants it
 * kept apart from the service's colour: urgency is the warning signal, the service
 * identifies where the work came from. Different families, not different shades. */
.u-low    { --edge: oklch(62% 0.03 285); --edge-fill: oklch(100% 0 0 / 0.08);
            --edge-ink: oklch(84% 0.02 285); --edge-ring: oklch(100% 0 0 / 0.18); }
.u-normal { --edge: oklch(78% 0.16 200); --edge-fill: oklch(78% 0.16 200 / 0.18);
            --edge-ink: oklch(90% 0.12 200); --edge-ring: oklch(78% 0.16 200 / 0.45); }
.u-high   { --edge: oklch(70% 0.2 25);   --edge-fill: oklch(70% 0.2 25 / 0.2);
            --edge-ink: oklch(86% 0.14 25); --edge-ring: oklch(70% 0.2 25 / 0.5); }

.todo-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.todo-head-right { display: flex; align-items: center; gap: 10px; }
.todo-count { font: 400 12px/1 var(--mono); color: var(--ink-dim); letter-spacing: 0.05em; }
.chip.file-task { height: 30px; padding: 0 11px 0 9px; }

/* Item 88: the task a delegate files for her. One column of labelled fields; the
 * urgency and the deadline share a row where there is room and stack where there is
 * not, and each kind chip carries its card's own hue on its icon. */
.task-field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.task-when { display: flex; flex-wrap: wrap; gap: 16px; }
.task-when > .task-field { flex: 1 1 150px; }
.task-field > .segment { align-self: flex-start; }
/* Item 120a (QA F-19): in the list column's sheet the urgency field shared its row at
   159 px and hid "Urgent" behind a sideways scroll with no bar. It keeps the width its
   three words need; the deadline wraps below it when the row is too narrow for both. */
.task-when > .task-field:has(> .segment) { flex: 0 0 auto; max-width: 100%; }
.task-when input[type="date"] { color-scheme: dark; }
.task-sheet .chips .chip svg { color: oklch(84% 0.13 var(--hue, 265)); }
.task-sheet .go { flex: none; }
.task-filed { display: flex; align-items: center; gap: 12px; color: var(--ink); line-height: 1.45; }

/* §B: "pasek postępu przesuwa się do przodu" as tasks close, so the width transitions
 * rather than jumping — the movement is the point. */
.progress {
  height: 4px; border-radius: 999px; background: oklch(100% 0 0 / 0.1); overflow: hidden;
}
.progress > div {
  height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, oklch(72% 0.2 300), oklch(78% 0.19 200), oklch(84% 0.19 165));
  box-shadow: 0 0 14px oklch(78% 0.19 220 / 0.7);
  transition: width 420ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.cards { display: flex; flex-direction: column; gap: 6px; }
.card {
  position: relative; display: flex; align-items: center; gap: 13px;
  width: 100%; min-height: 66px; padding: 12px 16px;
  border: 0; border-radius: var(--r-card); text-align: left;
  background: var(--raise-2); color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line);
  cursor: pointer; overflow: hidden;
  font: inherit;
  transition: background 180ms ease, box-shadow 180ms ease,
              transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.card:hover { background: var(--raise-3); transform: translateX(4px); }
/* The edge. Its colour is the urgency; its presence is not optional, so a card with no
 * urgency set still reads as a card rather than losing its left margin. */
.card::before {
  content: ""; position: absolute; left: 0; top: 10px; bottom: 10px;
  width: 3px; border-radius: 999px; background: var(--edge, var(--line));
}
.card.quiet { background: oklch(100% 0 0 / 0.04); box-shadow: inset 0 0 0 1px var(--line-soft); }
.card.quiet:hover { background: oklch(100% 0 0 / 0.1); }
.card-number {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font: 600 13px/1 var(--mono);
  background: var(--edge-fill, var(--raise-2)); color: var(--edge-ink, var(--ink-dim));
  box-shadow: inset 0 0 0 1px var(--edge-ring, var(--line));
}
.card-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.card-title { font-size: 16px; line-height: 1.4; letter-spacing: -0.005em; text-wrap: pretty; }
/* Item 120b (QA F-23): the words she was sent, one line of them, and the master the
   task is about, small, beside the title — so ten "Rework" cards are ten things. */
.card-detail { font: 400 13px/1.35 var(--sans); color: var(--ink-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-thumb { flex: none; width: 44px; height: 44px; border-radius: 10px; object-fit: cover;
  background: oklch(100% 0 0 / 0.08); box-shadow: inset 0 0 0 1px var(--line); }
.card-meta { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
/* The kind chip: what sort of action the task wants, before she opens it (§C). */
.card-kind {
  flex: none; width: 28px; height: 28px; border-radius: var(--r-chip);
  display: flex; align-items: center; justify-content: center;
  background: oklch(80% 0.17 var(--hue, 265) / 0.16); color: oklch(90% 0.13 var(--hue, 265));
}
.card-kind.plain { background: var(--raise); color: var(--ink-mute); }

/* An archive row's ✓ / ✗ */
.mark {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.mark.done { background: oklch(84% 0.19 165 / 0.2); color: var(--on-ink); }
.mark.rejected { background: oklch(70% 0.18 25 / 0.2); color: var(--warn-ink); }
.struck {
  font-size: 15px; line-height: 1.35; color: oklch(80% 0.02 285);
  text-decoration: line-through; text-decoration-color: oklch(60% 0.02 285);
  text-wrap: pretty;
}
.status-line {
  font-size: 10.5px; letter-spacing: 0.09em; text-transform: uppercase;
  font-family: var(--mono); color: oklch(70% 0.02 285);
}

/* --- a task, in full ----------------------------------------------------------- */

.detail-title {
  font-size: 19px; font-weight: 600; letter-spacing: -0.015em; line-height: 1.25;
  text-wrap: pretty;
}
.detail-heading { flex: 1; display: flex; flex-direction: column; gap: 7px; min-width: 0; }
/* Item 200: from the head to the newest message and back. The chat button carries a
   mark when there are questions she has not read; the way back up floats at the bottom
   edge of the scrolled body (a sticky, zero-height strip holds it) and appears only once
   there is somewhere to come back from. The arrow points down because it is drawn
   upside down — one icon, one meaning: "the other end". */
.jump-to-chat { position: relative; align-self: flex-start; }
.jump-to-chat .news {
  position: absolute; top: -5px; right: -5px; width: 17px; height: 17px; border-radius: 50%;
  display: none; font: 700 11px/17px var(--mono); text-align: center;
  color: oklch(18% 0.04 25); background: var(--warn);
  box-shadow: 0 0 0 2px oklch(22% 0.03 285);
}
.jump-to-chat.has-news .news { display: block; }
.to-top-wrap { position: sticky; bottom: 0; flex: none; height: 0; align-self: stretch; pointer-events: none; }
.to-top {
  position: absolute; right: 0; bottom: 8px; pointer-events: auto;
  transform: rotate(180deg) translateY(-6px); opacity: 0;
  background: oklch(28% 0.035 285); box-shadow: inset 0 0 0 1px var(--line), 0 10px 24px -10px oklch(8% 0.06 285 / 0.9);
  transition: opacity 160ms ease, transform 160ms ease;
}
.to-top-wrap.shown .to-top { opacity: 1; transform: rotate(180deg); }
.detail-text { font-size: 15px; line-height: 1.55; text-wrap: pretty; color: var(--ink-soft); }
.due { font-size: 11.5px; font-family: var(--mono); color: var(--ink-mute); }
/* D-3 / F-30: past its deadline, said in the warning colour and first on the list. */
.due.overdue { color: var(--warn-ink); }
.pill.overdue {
  color: var(--warn-ink); background: oklch(70% 0.18 25 / 0.16);
  box-shadow: inset 0 0 0 1px oklch(75% 0.16 25 / 0.45);
}
/* Item 196: an urgent task's tag, in the same red — urgency is the one tag that is a
   warning, so it borrows the warning's colour rather than sitting grey beside the kind. */
.pill.urgent {
  color: var(--warn-ink); background: oklch(70% 0.18 25 / 0.16);
  box-shadow: inset 0 0 0 1px oklch(75% 0.16 25 / 0.45);
}

/* Who asked. One card, so the question "who do I go back to" has one place. */
/* Who assigned it (item 198): one line over the questions, in the label's own voice,
   with the name legible — not shouted in the label's small capitals. The card it used
   to be (avatar, label, name, ~60 px) is gone: the person is who the thread is with. */
.thread-label { display: flex; align-items: baseline; gap: 7px; }
.thread-label b {
  font: 600 13px/1.2 var(--sans); letter-spacing: 0; text-transform: none; color: var(--ink);
}

/* The material on a task's detail (§D): what was asked about, and what came back,
   side by side — the comparison is the point. */
.material { display: flex; flex-wrap: wrap; gap: 8px; }
/* At most half a wide pane each (item 120a, QA F-36): alone, a cell took 470 px and
   shoved the Photo / Video tiles to the far edge. */
.material-cell { flex: 1 1 200px; max-width: 320px; min-width: 0; margin: 0; }
.material-frame { position: relative; }
.material-cell img, .material-cell video {
  width: 100%; height: auto; border-radius: 13px; display: block;
  background: oklch(80% 0.17 var(--hue, 265) / 0.14);
  box-shadow: inset 0 0 0 1px oklch(80% 0.17 var(--hue, 265) / 0.36);
  /* Capped, because a lone "asked about" cell is full frame width and a square photo
     then runs ~390px tall — it swallowed the screen and pushed the thread and the
     accept/decline buttons out of sight. Contained, not cropped (QA F-36): a portrait
     delivery showed as a strip across its middle, and what came back has to be seen
     whole to be judged. */
  max-height: 220px; object-fit: contain;
}
/* A lone preview is not a comparison, so it is not cropped to match anything: it shows
   the whole picture and takes the height it needs. */
.material-cell.solo img, .material-cell.solo video {
  object-fit: contain; max-height: 340px; background: oklch(100% 0 0 / 0.05);
}
.material-cell figcaption {
  margin-top: 6px; font: 400 9.5px/1 var(--mono);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-mute);
}
/* What we expect (item 99): the admin's instructions keep their line breaks, and the
   examples are smaller than the material — references to look at, not the thing she
   is judged on. Captions are sentences here, so they are not shouted. Item 197 took
   the heading off and gave the words a card in the task's own hue (`--hue` is the
   kind's, set on the view) — the one tinted surface on the detail, so it reads as the
   brief rather than as more of the description above it. */
.expected .detail-text {
  white-space: pre-line; padding: 14px 16px; border-radius: 14px;
  color: var(--ink);
  background: oklch(80% 0.17 var(--hue, 165) / 0.1);
  box-shadow: inset 0 0 0 1px oklch(84% 0.17 var(--hue, 165) / 0.28);
}
.examples .material-cell { flex: 0 1 150px; }
.examples .material-cell img, .examples .material-cell video { max-height: 180px; object-fit: cover; aspect-ratio: 4 / 5; }
.examples .material-cell figcaption { text-transform: none; letter-spacing: 0; font: 400 11px/1.35 var(--mono); }
/* Her team's versions (item 100b): the newest in full, earlier ones folded beneath it
   with why each went back. A version waiting for her answer is the one lit. */
.rounds .round { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px;
  border-radius: 13px; background: oklch(100% 0 0 / 0.04); box-shadow: inset 0 0 0 1px var(--line-soft); }
.rounds .round-submitted { box-shadow: inset 0 0 0 1px oklch(80% 0.14 165 / 0.55); }
.rounds .round-head { font: 400 10.5px/1.4 var(--mono); letter-spacing: 0.04em; color: var(--ink-mute); }
.rounds .round .detail-text { white-space: pre-line; }
.rounds .round-comment { font-size: 13.5px; line-height: 1.45; color: var(--warn, oklch(78% 0.14 40)); }
.rounds .earlier > summary { cursor: pointer; font: 400 11px/1.4 var(--mono); color: var(--ink-dim);
  padding: 4px 0; }
.rounds .earlier[open] { display: flex; flex-direction: column; gap: 8px; }
/* The team's WORK list (item 100c): whose panel each task is on, above its title, and
   the two groups named. The hand-in form under the versions: a big box for the words. */
.work-list .work-group { padding: 6px 4px 0; }
/* Item 165d: "1 to review · 2 to hand in · 1 to publish" is longer than the title's room —
   the count drops under the title whole instead of squeezing both onto two lines. */
.work-list .todo-head { flex-wrap: wrap; row-gap: 6px; }
.work-list .todo-head .screen-title, .work-list .todo-count { white-space: nowrap; }
.work-list .card-body > .work-for { align-self: flex-start; margin-bottom: 4px; }
.hand-in textarea { min-height: 7.5em; }
.hand-in .material-cell .chip { margin-top: 6px; }
/* One order across services (item 101b): a step that waits is quieter, and says so; the
   order's steps listed on each of them, the one on screen marked. */
.card.blocked { opacity: 0.72; }
.pill.waits { background: oklch(100% 0 0 / 0.06); color: var(--ink-dim); }
.order-steps { display: flex; flex-direction: column; gap: 4px; }
.order-step { display: flex; justify-content: space-between; gap: 12px; align-items: baseline;
  padding: 8px 12px; border-radius: 11px; border: 0; cursor: pointer; text-align: left;
  background: oklch(100% 0 0 / 0.04); color: var(--ink); font: inherit; }
.order-step:hover { background: oklch(100% 0 0 / 0.08); }
.order-step[aria-current="true"] { box-shadow: inset 0 0 0 1px var(--line); cursor: default; }
.order-title { font-size: 13.5px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.order-state { flex: none; font: 400 10.5px/1.3 var(--mono); color: var(--ink-mute); }
.check-line { display: flex; gap: 8px; align-items: center; margin-top: 8px;
  font: 400 12px/1.4 var(--mono); color: var(--ink-dim); }
/* ROADMAP 260/263: her "I don't want to write the descriptions" box on a task — its hint
   while unticked, one small line once ticked; the words below fold with it */
.leave-words { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px;
  border-radius: 12px; background: oklch(100% 0 0 / 0.04); box-shadow: inset 0 0 0 1px var(--line-soft);
  cursor: pointer; transition: padding 200ms ease; }
.leave-words.on { padding: 6px 10px; align-items: center; }
.leave-words input { flex: none; margin-top: 2px; width: 16px; height: 16px; accent-color: var(--on, currentColor); }
.leave-words.on input { margin-top: 0; }
.leave-words-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.leave-words-text b { font-weight: 500; font-size: 13.5px; color: var(--ink); transition: font-size 200ms ease; }
.leave-words.on .leave-words-text b { font-size: 12px; color: var(--ink-dim); }
.leave-words-hint { font: 400 11.5px/1.45 var(--mono); color: var(--ink-dim);
  max-height: 4em; overflow: hidden; transition: max-height 220ms ease, opacity 180ms ease; }
.leave-words.on .leave-words-hint { max-height: 0; opacity: 0; }
/* flex: none — in the scrolled column an overflow-hidden block would otherwise shrink */
.words-fold { flex: none; max-height: 1600px; overflow: hidden;
  transition: max-height 260ms ease, opacity 200ms ease; }
.words-fold.folded { max-height: 0; opacity: 0; margin: 0; padding: 0; border: 0; }
/* ROADMAP 263: past her files, what the task asked for — one line until opened */
.asked-fold > summary { cursor: pointer; font: 400 11px/1.4 var(--mono); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-faint); padding: 4px 0; }
.asked-fold[open] > summary { margin-bottom: 8px; }
/* ROADMAP 264: her To do read by her team — whose move, in one row */
.who-filter { display: flex; flex-wrap: wrap; gap: 6px; }
.who-filter .chip { width: auto; flex: none; }
/* ROADMAP 264: who wrote, in a task's chat of several */
.bubble-who { font: 600 10px/1.3 var(--mono); letter-spacing: 0.06em; color: var(--ink-dim); margin-bottom: 3px; }
.bubble-who.admin { color: oklch(80% 0.16 30); }
/* ROADMAP 264: the step that is yours on this screen — tinted, outlined, and the outline
   breathes a few times so the eye finds it */
.detail-block.attention { padding: 14px; border-radius: 14px;
  background: oklch(78% 0.14 200 / 0.12); box-shadow: inset 0 0 0 1.5px oklch(80% 0.15 200 / 0.75);
  animation: attention 1.6s ease-in-out 3; }
.detail-block.attention > .detail-label:first-child { color: oklch(86% 0.13 200); font-weight: 600; }
@keyframes attention {
  50% { box-shadow: inset 0 0 0 1.5px oklch(80% 0.15 200 / 0.75), 0 0 0 4px oklch(80% 0.15 200 / 0.25); }
}
/* ROADMAP 264: Send fills as her files travel — a light band, a spinner, the share */
.go.busy { position: relative; overflow: hidden; cursor: progress; }
.go.busy > svg { display: none; }
.go .send-fill { position: absolute; inset: 0 auto 0 0; width: 0; pointer-events: none;
  background: oklch(100% 0 0 / 0.35); transition: width 180ms ease; }
.go .spinner { width: 16px; height: 16px; border-radius: 50%; flex: none; position: relative;
  border: 2px solid currentColor; border-right-color: transparent;
  animation: spin 0.8s linear infinite; }
.go.busy > span:last-child { position: relative; }
@keyframes spin { to { transform: rotate(360deg); } }
.chip.quiet { background: transparent; box-shadow: inset 0 0 0 1px var(--line-soft); }
/* The filter bar (item 102b): a label over each row of chips, the count on each chip
   quieter than its name, and "Showing N of M" with the way back beside it. */
.task-filters { display: flex; flex-direction: column; gap: 10px; }
.filter-row { display: flex; flex-direction: column; gap: 6px; }
.chip .chip-count { margin-left: 6px; opacity: 0.6; font-variant-numeric: tabular-nums; }
.filter-count { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font: 400 11px/1.4 var(--mono); color: var(--ink-dim); }
/* An attachment that is only a name — the prototype's 74px tile. */
/* Neutral (QA F-36): in the task's own hue an "Action" task's tiles were red and read as
   an error rather than as a place to put a file. */
.file-tile {
  width: 74px; height: 74px; border-radius: 13px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  background: oklch(100% 0 0 / 0.07); color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line);
  border: 0; cursor: pointer;
}
.file-tile span { font: 400 9.5px/1 var(--mono); letter-spacing: 0.04em; }

.actions { display: flex; gap: 8px; }
.actions .go { flex: 1; }
/* Item 177: saving where else a fan can be reached, inside his sheet. Stacked, not two to a
   line: the sheet is about 300 px wide and the kind list needs most of it — side by side
   left the field itself 22 px (seen live). */
/* Item 213: each saved account on its own line — the kind's pill and his note and day. */
.contact-marks { display: flex; flex-direction: column; gap: 6px; }
.contact-mark { display: flex; align-items: center; gap: 8px; min-width: 0; }
.contact-mark .aside { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.contact-form { display: grid; gap: 8px; margin-top: 10px; }
.contact-row { display: grid; gap: 8px; }
.contact-row select, .contact-row input { width: 100%; min-width: 0; }
.contact-form input, .contact-form select {
  font: 400 14px/1.3 var(--sans); padding: 9px 10px; border-radius: 10px;
  background: var(--raise); color: var(--ink); border: 1px solid var(--line);
}
.contact-form .go { justify-self: start; }

/* --- your account (item 185) -------------------------------------------------
 * Three blocks, and the last two each take a password: the field and its button stack,
 * because they are one thought and a row of them reads as a form somebody must fill in
 * before anything on the page works. */
.detail-block > input[type="password"] { width: 100%; margin-top: 8px; }
.detail-block > .go { margin-top: 8px; }

/* --- one message to many (item 183) ------------------------------------------
 * The send sheet and, under it, what has already gone out. A batch is delivered over
 * hours, so every row carries its own two numbers rather than one status word. */
.batches { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
.batch {
  padding: 9px 11px; border-radius: 12px;
  background: var(--raise); box-shadow: inset 0 0 0 1px var(--line);
}
.batch-said {
  font: 400 13px/1.4 var(--sans); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.batch-foot {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-top: 5px;
  font: 400 11px/1.3 var(--mono); color: var(--ink-dim);
}
.batch-foot b { font: 400 11px/1.3 var(--mono); color: var(--ink); }
/* The verification task's own blocks (2026-10-13), spaced as the task's other blocks. */
.verification { display: flex; flex-direction: column; gap: 16px; }
/* The send sheet's own field and audience row. */
.sheet-body > textarea {
  width: 100%; min-width: 0; font: 400 14.5px/1.45 var(--sans);
}
.sheet-body > .chips { margin-top: 10px; align-items: center; }
/* Who a narrowed send reaches (2026-10-13): one ticked line per fan, scrolling on its
 * own so a long audience does not push the send button off the sheet. */
.mass-sheet .sheet-body > * { flex: none; }
/* Narrowed, the sheet takes the whole pane height so a big screen shows a long list,
 * and the list is what grows and scrolls — never the page around it. */
.sheet.over.mass-sheet.tall { top: 12px; max-height: none; }
.mass-sheet .sheet-body > .mass-pick {
  flex: 1 1 auto; min-height: 200px; display: flex; flex-direction: column; gap: 8px;
}
.mass-sheet .note-line:empty { display: none; }
.mass-when { align-items: center; }
/* The send button in two lines: what it does, then to whom, when and in which zone. */
.go.two-line {
  height: auto; min-height: 48px; padding: 7px 16px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
}
.go.two-line .go-main { line-height: 1.25; }
.go.two-line .go-sub { font: 400 11.5px/1.3 var(--mono); opacity: 0.8; }
.mass-sheet .note-line.held { color: var(--warn-ink); }
.mass-when > input { flex: 0 1 230px; min-width: 0; height: 40px; margin: 0; color-scheme: dark; }
.batch-foot > .chip { margin-left: auto; }
.mass-tools { flex: none; display: flex; gap: 6px; align-items: center; height: 46px; }
.mass-slot { flex: 1 1 auto; min-width: 0; display: flex; }
.mass-slot > input, .mass-slot > select { flex: 1 1 auto; min-width: 0; height: 46px; margin: 0; }
.mass-tools > .chip { flex: none; }
.mass-people {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 2px 6px;
  border-bottom: 1px solid var(--line-soft);
}
.mass-people .check-line > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sheet-body > .go { margin-top: 12px; }

/* --- what the team knows about a fan (item 182) -----------------------------
 * In the customer sheet, above the contacts: a short list of sentences with who wrote
 * each, and one field to add another. The sheet is 300 px wide, so nothing here is a
 * row of controls side by side. */
.notes { display: flex; flex-direction: column; gap: 8px; margin-top: 6px; }
.note-row {
  padding: 8px 10px; border-radius: 12px;
  background: var(--raise); box-shadow: inset 0 0 0 1px var(--line);
}
.note-said { font: 400 13px/1.45 var(--sans); color: var(--ink); overflow-wrap: anywhere; }
.note-foot {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 5px;
}
.note-foot span { font: 400 11px/1 var(--mono); color: var(--ink-dim); }
.note-foot .chip.forget { margin-left: auto; }
/* Armed on the first press, gone on the second — it says "Sure?" in between, so the
   colour is the only other warning it needs. */
.note-foot .chip.forget[data-armed="1"] { color: var(--warn-ink); }
/* Item 212: the notepad — one field, its author line, Save / Cancel once she has typed,
   and the earlier versions folded under it. */
.notepad { width: 100%; min-height: 128px; resize: vertical; margin-top: 6px;
  font: 400 13px/1.5 var(--sans); }
.notepad-meta { margin-top: 4px; }
.notepad-actions { justify-content: flex-end; margin-top: 8px; }
.notepad-actions .go { flex: 0 0 auto; width: auto; padding: 0 20px; }
.note-history { margin-top: 8px; }
.note-history summary { cursor: pointer; font: 500 11px/1 var(--mono); letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-dim); }
.note-history .notes { margin-top: 8px; }
.note-form { display: grid; gap: 8px; margin-top: 10px; }
.note-form input {
  width: 100%; min-width: 0; font: 400 14px/1.3 var(--sans); padding: 9px 10px;
  border-radius: 10px; background: var(--raise); color: var(--ink);
  border: 1px solid var(--line);
}
/* The shared form rule stretches every `.go` to the full width, which in a 300 px
   sheet makes "Add" the loudest thing on the customer's card — louder than what he is
   worth. It is a small action beside a one-line field. */
.note-form .go { justify-self: start; width: auto; padding: 0 20px; }

/* --- searching a conversation (item 181) -----------------------------------
 * The bar and its hits live above the log and cover it: a conversation and its search
 * are one screen. `.find-hits` is its own scroller, so the header and the composer stay
 * where they are while the results move. */
.find-wrap { display: contents; }
.find {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-bottom: 1px solid var(--line); background: var(--raise);
}
.find input {
  flex: 1 1 auto; min-width: 0; width: auto;
  font: 400 14px/1.3 var(--sans); padding: 8px 10px; border-radius: 10px;
  background: var(--raise); color: var(--ink); border: 1px solid var(--line);
}
.find-says {
  flex: none; font: 400 11px/1 var(--mono); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-dim);
}
.find-hits {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 8px; padding: 10px 12px;
}
.hit {
  display: flex; flex-direction: column; gap: 4px; text-align: left; cursor: pointer;
  padding: 9px 11px; border: 0; border-radius: var(--r-control);
  background: var(--raise); color: var(--ink); box-shadow: inset 0 0 0 1px var(--line);
}
.hit:hover { background: var(--raise-hover); }
.hit-head { display: flex; align-items: baseline; gap: 8px; }
.hit-head b { font: 600 12.5px/1.2 var(--sans); }
.hit-head span { font: 400 11px/1 var(--mono); color: var(--ink-dim); }
.hit-said { font: 400 13px/1.45 var(--sans); color: var(--ink); }
.hit mark {
  background: oklch(85% 0.16 95 / 0.35); color: inherit; border-radius: 3px; padding: 0 1px;
}
/* Where the log has been opened: the bubble that was searched for, lit for a moment. */
.bubble.found { animation: found-glow 2.4s ease-out 1; }
@keyframes found-glow {
  0%, 60% { box-shadow: 0 0 0 2px oklch(85% 0.16 95 / 0.55); }
  100% { box-shadow: 0 0 0 2px oklch(85% 0.16 95 / 0); }
}
@media (prefers-reduced-motion: reduce) {
  .bubble.found { animation: none; box-shadow: 0 0 0 2px oklch(85% 0.16 95 / 0.55); }
}
/* Parked in the past: what is on screen is not the end of the conversation, and a
   message arriving now is counted instead of drawn under one from March. */
.parked {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 12px; border-bottom: 1px solid var(--line);
  background: oklch(85% 0.16 95 / 0.12);
}
.parked span { flex: 1 1 auto; min-width: 0; font: 400 12px/1.35 var(--sans); color: var(--ink); }
/* The wide search, on the FANS list: one field across the row it sits in. */
.find-all {
  flex: 1 1 auto; min-width: 0; width: auto;
  font: 400 14px/1.3 var(--sans); padding: 9px 11px; border-radius: 10px;
  background: var(--raise); color: var(--ink); border: 1px solid var(--line);
}
/* On the list it is not the scroller — the screen is — so it lays itself out and the
   page scrolls, unlike the copy that covers a conversation's log. */
.find-hits.inset { flex: none; overflow: visible; padding: 10px 0; }

/* --- saved replies (item 180) ----------------------------------------------
 * A list of sentences, each one a press away from the box. The row is a flex, not a
 * card grid: the two controls beside it are an edit and a remove, and both belong to
 * the same object the text does. */
.replies { display: flex; flex-direction: column; gap: 10px; }
.reply-top { display: flex; gap: 8px; }
.reply-list { display: flex; flex-direction: column; gap: 8px; }
.reply-find {
  font: 400 14px/1.3 var(--sans); padding: 9px 11px; border-radius: 10px;
  background: var(--raise); color: var(--ink); border: 1px solid var(--line);
}
.reply { display: flex; align-items: stretch; gap: 6px; }
.reply-take {
  flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column;
  align-items: flex-start; gap: 3px; text-align: left; cursor: pointer;
  padding: 10px 12px; border: 0; border-radius: var(--r-control);
  background: var(--raise); color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line);
}
.reply-take:hover:not(:disabled) { background: var(--raise-hover); }
.reply-take:disabled { cursor: default; }
.reply-take b { font: 600 13.5px/1.2 var(--sans); }
/* One line of it, and the rest is what the pencil is for: a preview that wraps to four
   lines turns a list of twenty replies into a page nobody scans. */
.reply-take span {
  max-width: 100%; font: 400 12px/1.35 var(--sans); color: var(--ink-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.reply .icon-button.sm { flex: none; align-self: center; }
.reply-form { display: grid; gap: 8px; }
.reply-form input, .reply-form textarea {
  width: 100%; min-width: 0; font: 400 14px/1.4 var(--sans); padding: 9px 10px;
  border-radius: 10px; background: var(--raise); color: var(--ink);
  border: 1px solid var(--line);
}
.reply-form-foot { display: flex; gap: 8px; align-items: center; justify-content: flex-end; }
.reply-form-foot .go { flex: none; width: auto; padding: 0 18px; }

/* Item 112: declining asks why, in the foot where the button was. */
/* Item 199: the optional note over "Hand in" — one line, so it costs the foot what a
   composer's line costs and never reads as a form to fill in. */
.hand-in-note { min-height: 0; height: auto; overflow: hidden; }
/* Item 205: now or at an hour, beside the button that sends a task out. The choice is a
   select shaped like the chips around it; the row it opens — date, 24-hour time as two
   lists, zone — wraps on a phone. */
.actions .when-pick { flex: none; width: auto; height: 46px; padding: 0 36px 0 14px; }
.when-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.when-row input[type="date"] { flex: 1 1 150px; height: 40px; padding: 0 10px; }
.when-row select { height: 40px; padding: 0 28px 0 10px; width: auto; flex: none; }
.when-row select[aria-label="Time zone"] { flex: 1 1 160px; min-width: 0; }
.when-colon { font: 600 15px/1 var(--mono); color: var(--ink-dim); }
.decline-form { display: flex; flex-direction: column; gap: 8px; }
.decline-form textarea { min-height: 76px; }
.decline-form .actions { justify-content: flex-end; align-items: center; }
/* The decline's reason on its archive card: one line, the rest on the task. */
.card-reason {
  margin-top: 4px; font: 400 12px/1.4 var(--sans); color: var(--ink-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* --- bubbles ------------------------------------------------------------------
 * §E: "History with hub/creator vs fan visually split". Which *side* a bubble sits
 * on is the whole of that split — a colour alone has to be read, a side does not. */

.thread, .log-lines {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: 8px;
}
.log-lines { overflow-y: auto; gap: 10px; padding: 4px 18px 14px; }
/* A short conversation sits at the bottom, where the next message will arrive, rather
   than floating at the top of an empty pane. `margin-top: auto` and not
   `justify-content`, which fights the scrollbar once the log is longer than the pane. */
.log-lines > :first-child { margin-top: auto; }
.bubble {
  max-width: 84%; padding: 12px 15px; border-radius: 16px;
  font: 400 15px/1.45 var(--sans); text-wrap: pretty; word-break: break-word;
}
.bubble.mine {
  align-self: flex-end;
  background: oklch(80% 0.17 var(--hue, 200) / 0.9); color: var(--ink-inverse);
  border-bottom-right-radius: 5px;
}
.bubble.theirs {
  align-self: flex-start;
  background: var(--raise-2); color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line);
  border-bottom-left-radius: 5px;
}
/* Roadmap 79: their typing, drawn where their next message will be. Three dots and
   no words — it is on screen for four seconds at a time and must not be read, only
   noticed. */
.bubble.typing {
  display: flex; align-items: center; gap: 5px;
  width: fit-content; padding: 15px 16px;
}
.bubble.typing i {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ink-mute);
  animation: typing-dot 1.25s ease-in-out infinite;
}
.bubble.typing i:nth-child(2) { animation-delay: 0.16s; }
.bubble.typing i:nth-child(3) { animation-delay: 0.32s; }
@keyframes typing-dot {
  0%, 60%, 100% { opacity: 0.32; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}

/* What a content request is asking for, and where it has got to. */
.bubble-tag {
  display: flex; align-items: baseline; gap: 6px; margin-bottom: 5px;
  font: 400 10px/1 var(--mono); letter-spacing: 0.12em; text-transform: uppercase;
  opacity: 0.75;
}
.bubble-tag b { font-weight: 600; }

/* A fan's order (item 154c): a card on his side of the conversation, because it is his
   message, with the order's state as the headline and its buttons under the money. A
   closed one steps back — it is history now, not something waiting. */
.order-card {
  align-self: flex-start; width: min(360px, 92%);
  display: flex; flex-direction: column; gap: 8px;
  padding: 13px 15px 10px; border-radius: 16px; border-bottom-left-radius: 5px;
  background: var(--raise-2); color: var(--ink);
  box-shadow: inset 0 0 0 1px oklch(84% 0.19 165 / 0.45);
}
.order-card.closed { box-shadow: inset 0 0 0 1px var(--line); opacity: 0.82; }
.order-head { display: flex; align-items: center; gap: 7px; font: 600 14px/1.2 var(--sans); }
.order-head svg { flex: none; color: var(--on); }
.order-state {
  margin-left: auto; font: 400 10px/1 var(--mono); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-mute);
}
.order-says { font: 400 14px/1.4 var(--sans); text-wrap: pretty; }
.order-options { display: flex; flex-wrap: wrap; gap: 5px; }
.order-note { font: italic 400 14px/1.4 var(--sans); color: var(--ink-dim); word-break: break-word; }
.order-money {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
  font: 400 12px/1.3 var(--mono); font-variant-numeric: tabular-nums; color: var(--on);
}
.order-lapse { color: var(--ink-mute); }
.order-moves { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 2px; }
.order-go, .order-plain {
  display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 12px;
  border: 0; border-radius: 999px; cursor: pointer; font: 600 13px/1 var(--sans);
}
.order-go { color: var(--ink-inverse); background: var(--go-grad); }
.order-go.wide { flex: none; width: 100%; justify-content: center; height: 44px; }
.order-plain { color: var(--ink); background: var(--raise); box-shadow: inset 0 0 0 1px var(--line); }
.order-plain.warn { color: var(--warn-ink); }
.order-go[disabled], .order-plain[disabled] { opacity: 0.45; cursor: default; }
.order-price {
  width: 92px; height: 34px; padding: 0 10px; border-radius: 10px;
  font: 600 14px/1 var(--mono); color: var(--ink);
  background: var(--raise); border: 0; box-shadow: inset 0 0 0 1px var(--line);
}
.order-hint { flex-basis: 100%; font: 400 12.5px/1.35 var(--sans); color: var(--ink-dim); }
.order-card .when { font: 400 10px/1 var(--mono); opacity: 0.6; align-self: flex-end; }
.chip.order-open { align-self: flex-start; max-width: 100%; }
/* The sheet spans the conversation, so its library tiles are thumbnails, not posters. */
.sheet.order-sheet .tiles,
.sheet.archive-pick .tiles { grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); }

/* Money arriving is not conversation, so it is not drawn as a bubble. It is the same
   mint the rest of the panel uses for money, and it says what it was. */
.money-note-bubble {
  display: inline-flex; align-items: baseline; gap: 8px;
  padding: 9px 14px; border-radius: 999px;
  font: 400 13px/1 var(--sans);
  color: var(--on-ink); background: oklch(84% 0.19 165 / 0.12);
  box-shadow: inset 0 0 0 1px oklch(84% 0.19 165 / 0.35);
}
.money-note-bubble.mine { align-self: flex-end; }
.money-note-bubble.theirs { align-self: flex-start; }
.money-note-bubble svg { align-self: center; flex: none; }
.money-note-bubble b { font-weight: 600; }
.money-note-bubble .amount {
  font: 600 14px/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--on);
}
.money-note-bubble .when { font: 400 10px/1 var(--mono); opacity: 0.6; }

/* Paid media nobody has unlocked. The blurred placeholder is the entire payload the
   server sends — there is no url — so this says what it is and what it costs rather
   than offering a button that cannot fire: unlocking is a purchase, and it happens on
   the portal, not here. */
.locked {
  position: relative; width: 220px; max-width: 100%; aspect-ratio: 4 / 3;
  margin-top: 6px; border-radius: 10px; overflow: hidden;
  background: oklch(100% 0 0 / 0.08); box-shadow: inset 0 0 0 1px var(--line);
  display: flex; align-items: center; justify-content: center;
}
.locked img { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; filter: blur(14px) saturate(120%); transform: scale(1.15); }
/* Item 137 (QA F-50): what the *selling* side needs to know about a paid file — its
   price, and whether he has paid it. Under the picture rather than over it: a label on
   the image is a label on the thing she is trying to look at. */
/* The machine's half of a refusal (item 138): kept, because a bug report is grepped for
   it, and quiet, because she is not the one who reads it. */
.note-code {
  margin-left: 6px; padding: 1px 6px; border-radius: 999px;
  font: 400 10px/1.5 var(--mono); color: var(--ink-mute);
  background: oklch(100% 0 0 / 0.06);
}
/* A conversation says the day once, above its first message (item 141, QA F-54), and
   each bubble says its hour. */
/* The archive's tag row holds its line while empty (item 142, QA F-55), so the first
   purpose ticked does not push the chips under the pointer. */
.card-meta.reserved { min-height: 22px; }
.day-divider {
  align-self: center; margin: 14px 0 6px; padding: 3px 11px; border-radius: 999px;
  font: 500 10px/1.4 var(--mono); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-mute); background: oklch(100% 0 0 / 0.06);
}
.paid-line {
  display: inline-flex; align-items: center; gap: 4px; margin-top: 5px;
  padding: 2px 8px 2px 6px; border-radius: 999px;
  font: 500 10.5px/1.4 var(--mono); letter-spacing: 0.04em;
  color: oklch(88% 0.09 85); background: oklch(88% 0.09 85 / 0.14);
  box-shadow: inset 0 0 0 1px oklch(88% 0.09 85 / 0.3);
}
/* Paid for, so it is money in rather than money asked for — the mint the rest of the
   panel uses for what has arrived. */
.paid-line.bought {
  color: var(--on); background: oklch(84% 0.19 165 / 0.14);
  box-shadow: inset 0 0 0 1px oklch(84% 0.19 165 / 0.3);
}
.locked-label {
  position: relative; display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 12px; border-radius: 999px;
  font: 400 11px/1 var(--mono); letter-spacing: 0.1em; text-transform: uppercase;
  color: oklch(96% 0.01 285); background: oklch(18% 0.03 285 / 0.72);
  backdrop-filter: blur(4px);
}
/* A video before it is playing: its poster and a play badge, so the browser's control
   bar — the one surface the design cannot reach — stays out of the conversation until
   somebody is actually using it. */
.clip {
  position: relative; display: block; width: 300px; max-width: 100%; margin-top: 6px;
  border: 0; padding: 0; border-radius: 10px; overflow: hidden; cursor: pointer;
  aspect-ratio: 16 / 10; background: oklch(100% 0 0 / 0.1);
  box-shadow: inset 0 0 0 1px var(--line);
}
.clip img { width: 100%; height: 100%; object-fit: cover; display: block; }
.clip-play {
  position: absolute; inset: 0; margin: auto; width: 46px; height: 46px;
  display: flex; align-items: center; justify-content: center; border-radius: 50%;
  color: var(--ink-inverse); background: oklch(96% 0.01 285 / 0.92);
  box-shadow: 0 6px 20px -6px oklch(8% 0.06 285 / 0.9);
}
.clip:hover .clip-play { background: oklch(100% 0 0); }

/* The voice note. Its own control rather than the browser's, which is a light grey
   widget with its own typography and its own overflow menu. */
.voice {
  display: flex; align-items: center; gap: 10px;
  width: 240px; max-width: 100%; margin-top: 6px;
  padding: 9px 13px; border: 0; border-radius: 999px; cursor: pointer;
  color: inherit; background: oklch(100% 0 0 / 0.14);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.16);
}
.bubble.mine .voice { background: oklch(22% 0.04 285 / 0.14); box-shadow: inset 0 0 0 1px oklch(22% 0.04 285 / 0.2); }
.voice svg { flex: none; }
.voice-bar {
  flex: 1; height: 3px; border-radius: 999px; overflow: hidden;
  background: currentColor; opacity: 0.28;
}
.voice-bar > i { display: block; height: 100%; width: 0; background: currentColor; }
.voice.playing .voice-bar { opacity: 0.4; }
.voice-time {
  flex: none; font: 400 11px/1 var(--mono); font-variant-numeric: tabular-nums;
  opacity: 0.75;
}

.bubble .when, .bubble small {
  display: block; margin-top: 4px; font: 400 10px/1 var(--mono); opacity: 0.6;
}
/* "Unsend" (2026-10-13): small and under the hour, on her bubble while it is unread —
   a correction, not a call to action. */
.bubble .unsend {
  display: inline-block; margin-top: 6px; padding: 3px 8px; border: 0; border-radius: 8px;
  font: 600 10.5px/1.2 var(--mono); letter-spacing: 0.04em; text-transform: uppercase;
  color: inherit; background: oklch(22% 0.04 285 / 0.16); cursor: pointer;
}
.bubble .unsend:hover { background: oklch(22% 0.04 285 / 0.26); }
.bubble .unsend:disabled { opacity: 0.5; cursor: default; }
/* Capped: a bubble is 78% of the column, and on a desktop that made a snapshot 530px
   wide — the loudest thing in the conversation and bigger than the message it came
   with. */
/* `width`, not `max-width`: a bubble sized to its content takes the picture's
   *intrinsic* width when only a max is set, which left a 300px photo in a 360px
   bubble. Both, in the end — the `max-width` is what the *bubble* is measured by.
   A percentage is ignored while a shrink-to-fit box works out how wide it is, so a
   1200px photo drawn 300px wide still asked for a 488px bubble and left 158px of
   green to the right of it (Kamil, seen live 2026-09-09). The max clamps that
   question; the `min()` still keeps the picture inside a narrow pane. */
/* `height: auto` because the <img> now carries its file's width and height (84), which
   is what reserves its box before it loads; without it the attribute *is* the height. */
/* Item 116 (QA F-13): `width: min(100%, 300px)` has nothing to resolve its percentage
   against while the bubble is still working out its own width, so before a picture
   loaded its box collapsed to ~40 px, the log scrolled to the bottom, and the picture
   then grew past it — cutting off the bubble and its time. A definite 300 px, capped
   by the bubble, reserves the real box from the file's own width and height; a picture
   still on its way shows its blurred placeholder (or a quiet fill), not the bubble's
   flat colour. */
.bubble-media { width: 300px; max-width: 100%; max-height: 340px; height: auto;
  object-fit: cover; border-radius: 10px; margin-top: 6px; display: block;
  background: oklch(100% 0 0 / 0.16) center / cover no-repeat; }

/* --- CHAT (§E) ----------------------------------------------------------------- */

/* The scope tabs: one track, two halves, each with its own counter colour. */
.scope {
  display: flex; gap: 4px; padding: 4px; border-radius: 999px;
  background: var(--raise); box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.1);
}
.scope-tab {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 42px; border: 0; border-radius: 999px; cursor: pointer;
  font: 600 12.5px/1 var(--sans); letter-spacing: 0.02em;
  background: transparent; color: oklch(76% 0.02 285);
  transition: background 180ms ease, color 180ms ease;
}
.scope-tab[aria-selected="true"] { background: oklch(100% 0 0 / 0.16); color: var(--ink-strong); }
.scope-tab.fans[aria-selected="true"] { box-shadow: inset 0 0 0 1px oklch(84% 0.19 165 / 0.5); }
.scope-tab.team[aria-selected="true"] { box-shadow: inset 0 0 0 1px oklch(80% 0.17 300 / 0.5); }
.scope-count {
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  font: 600 10px/1 var(--mono); color: var(--ink-inverse);
}
.scope-count.fans { background: var(--on); }
.scope-count.team { background: var(--team); }

.list-controls {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex-wrap: wrap;
}

/* A conversation row. Same skeleton for a fan and for a colleague; the colleague's
   simply has no money on it, which §E asks for and is the point. */
.fan {
  position: relative; display: flex; align-items: flex-start; gap: 13px;
  width: 100%; padding: 13px 16px; text-align: left;
  border: 0; border-radius: var(--r-card); cursor: pointer; overflow: hidden;
  background: oklch(100% 0 0 / 0.04); color: var(--ink); font: inherit;
  box-shadow: inset 0 0 0 1px var(--line-soft);
  transition: background 180ms ease, transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.fan:hover { background: var(--raise-3); transform: translateX(4px); }
/* Waiting on her: brighter, and the edge lights up. A row she has answered recedes. */
.fan.waiting { background: var(--raise-2); box-shadow: inset 0 0 0 1px var(--line); }
.fan::before {
  content: ""; position: absolute; left: 0; top: 10px; bottom: 10px;
  width: 3px; border-radius: 999px; background: oklch(80% 0.17 var(--hue, 265));
}

.avatar { position: relative; flex: none; width: 36px; height: 36px; border-radius: 50%;
  display: grid; place-items: center;
  background: oklch(80% 0.17 var(--hue, 265) / 0.18);
  box-shadow: inset 0 0 0 1px oklch(80% 0.17 var(--hue, 265) / 0.45);
  font: 600 12.5px/1 var(--mono); color: oklch(90% 0.13 var(--hue, 265)); }
/* The green dot §E asks for. On the avatar rather than beside the name, so a long
   nickname cannot push it out of the row. */
.dot { position: absolute; right: -1px; bottom: -1px; width: 11px; height: 11px;
  border-radius: 50%; background: var(--live);
  box-shadow: 0 0 0 2.5px var(--frame-ink), 0 0 12px oklch(84% 0.19 145 / 0.9); }

.fan-mid { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
/* No wrapping: a long nickname must shorten rather than push its service badge onto a
   second line, which made every row in the list a different height. */
.fan-top { display: flex; align-items: baseline; gap: 8px; min-width: 0; flex-wrap: nowrap; }
.fan-top b { flex: 0 1 auto; min-width: 0; font: 600 15px/1.2 var(--sans);
  letter-spacing: -0.005em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fan-top .pill { flex: none; }
.fan.answered .fan-top b { color: oklch(84% 0.02 285); font-weight: 500; }
.fan-last { display: block; font: 400 14px/1.4 var(--sans); color: oklch(86% 0.02 285);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fan.answered .fan-last { color: oklch(70% 0.02 285); }
/* Item 118 (QA F-18): the address of a colleague who shares a name, under the name. */
.fan-detail { display: block; margin-top: 1px; font: 400 11px/1.35 var(--mono);
  color: oklch(66% 0.02 285); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.fan-right { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.fan-when { font: 400 11px/1 var(--mono); color: oklch(68% 0.02 285); }
/* Waiting for her reply: the design's mark exactly — an 8px dot in the row's own hue,
   glowing, gone once she has answered (Kamil, 2026-09-10: "follow the design exactly
   as drawn"). It replaced a mint count badge, which read as one more piece of money
   beside the amount and was never in the design. */
.waiting-dot { flex: none; width: 8px; height: 8px; border-radius: 50%;
  background: oklch(80% 0.17 var(--hue, 265));
  box-shadow: 0 0 12px oklch(80% 0.17 var(--hue, 265)); }

/* --- the conversation ---------------------------------------------------------- */

.convo-head { flex: none; display: flex; align-items: center; gap: 12px; padding: 16px 18px 14px; }
/* Not `flex: 1`: §3C puts the customer button "po prawej stronie nazwy użytkownika",
   and a header that stretched the name block pushed it 500px away on a desktop. */
.convo-who { flex: 0 1 auto; display: flex; align-items: baseline; gap: 8px; min-width: 0; }
/* Item 214: what he has earned her, beside his name while she writes — the list's pill. */
.convo-who .convo-worth { flex: none; }
.convo-who b { font: 600 16px/1.2 var(--sans); letter-spacing: -0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Item 181 put a fourth control in this row, and on a phone the fan's name lost to it:
   "disney135" was drawn as "d…". The portal pill is the part that can go — which portal
   a fan came from is on his row in the list and in his sheet, and the name is the one
   thing the header exists to say. */
@media (max-width: 460px) {
  .convo-head { gap: 8px; }
  .convo-who > .pill { display: none; }
  .convo-who { flex: 1 1 auto; }
}
.turbo-pill {
  flex: none; margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 11px; border-radius: 999px;
  font: 400 10.5px/1 var(--mono); letter-spacing: 0.1em; text-transform: uppercase;
  color: oklch(24% 0.05 285); background: var(--go-grad);
  box-shadow: 0 0 20px -4px oklch(82% 0.18 185 / 0.8);
}

/* §E's context card: what the conversation started from. A quiet strip between the
 * header and the messages — it names a thing, it does not compete with them. */
.context {
  flex: none; margin: 0 18px 14px; padding: 13px 15px; border-radius: 14px;
  display: flex; align-items: center; gap: 13px;
  background: oklch(100% 0 0 / 0.07); box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.13);
}
.context-thumb {
  flex: none; width: 42px; height: 42px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  color: oklch(88% 0.14 var(--hue, 200));
  background: oklch(80% 0.17 var(--hue, 200) / 0.16);
  box-shadow: inset 0 0 0 1px oklch(80% 0.17 var(--hue, 200) / 0.4);
}
.context-body { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.context-label {
  font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase;
  font-family: var(--mono); color: var(--ink-mute);
}
.context b { font-size: 14.5px; font-weight: 600; letter-spacing: -0.005em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.context .card-meta { margin-top: 2px; }

.composer { display: flex; align-items: center; gap: 8px; }
/* Both children have to be told to stop wanting the whole row: the shared form rule
   sets `width: 100%` on inputs *and* `button.go`, which is right everywhere the panel
   stacks controls and wrong the one place it puts two side by side. */
.composer input, .composer textarea { flex: 1 1 0; width: auto; min-width: 0; }
.composer button { flex: none; width: auto; }
.composer .go { flex: 1 1 auto; height: 46px; }
.composer .icon-button { width: 46px; height: 46px; border-radius: var(--r-control); }
.composer .skip {
  flex: none; display: inline-flex; align-items: center; gap: 7px;
  height: 46px; padding: 0 15px; border: 0; border-radius: var(--r-control);
  cursor: pointer; font: 400 11.5px/1 var(--mono); letter-spacing: 0.1em;
  text-transform: uppercase; color: oklch(84% 0.02 285);
  background: oklch(100% 0 0 / 0.07); box-shadow: inset 0 0 0 1px var(--line);
}
.composer .skip:hover { background: var(--raise-hover); }
/* --- the composer's one attachment (roadmap 78) --------------------------------
 * A row under the box rather than a tray over it: the three buttons are always the
 * same three, and what is attached sits next to them where the send button can be
 * seen at the same time. */
.attach { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 6px; }
/* The attachment buttons and Send on one line (2026-09-28). Send is sized to the icons
   beside it rather than the full-width 46px bar it was on a row of its own. */
.compose-bar { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px; margin-top: 6px; }
.compose-bar > .attach { flex: 1 1 auto; margin-top: 0; }
.compose-bar > .composer { margin-left: auto; }
.compose-bar .attach-buttons .icon-button.sm { width: 32px; height: 32px; }
.compose-bar .composer .go { flex: none; height: 32px; padding: 0 16px; font-size: 14px; }
.compose-bar .composer .icon-button, .compose-bar .composer .skip { height: 32px; }
.compose-bar .composer .icon-button { width: 32px; }
.attach-buttons { display: flex; gap: 6px; }
/* Item 215: a recording in progress — a pulsing red dot, the word, the clock against
   the cap, and Stop — on its own line of the attachments row, the whole time. */
.recording-strip {
  flex-basis: 100%; display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; border-radius: 12px;
  background: oklch(52% 0.18 25 / 0.14); box-shadow: inset 0 0 0 1px oklch(70% 0.18 25 / 0.45);
}
.rec-dot {
  width: 10px; height: 10px; border-radius: 50%; background: var(--warn);
  box-shadow: 0 0 0 0 oklch(72% 0.19 25 / 0.6); animation: rec-pulse 1.2s ease-out infinite;
}
@keyframes rec-pulse {
  0% { box-shadow: 0 0 0 0 oklch(72% 0.19 25 / 0.6); }
  100% { box-shadow: 0 0 0 9px oklch(72% 0.19 25 / 0); }
}
.rec-word { font: 600 11px/1 var(--mono); letter-spacing: 0.12em; text-transform: uppercase; color: var(--warn-ink); }
.rec-clock { font: 500 13px/1 var(--mono); font-variant-numeric: tabular-nums; color: var(--ink); }
.recording-strip .chip { margin-left: auto; }
.icon-button.recording { color: var(--warn-ink); background: oklch(52% 0.18 25 / 0.22);
  box-shadow: inset 0 0 0 1px oklch(70% 0.18 25 / 0.6); }
@media (prefers-reduced-motion: reduce) { .rec-dot { animation: none; } }
/* What she attached, shown rather than named. The name and the size are the tile's
   title — reachable, and not taking a third of the row. */
.attach-pending { display: flex; align-items: center; gap: 8px; }
.attach-tile {
  position: relative; flex: none;
  width: 48px; height: 48px; border-radius: 12px; overflow: hidden;
  background: var(--raise); box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.14);
}
.attach-tile img, .attach-tile video {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* A voice note has no picture, so the tile is its length. */
.attach-tile.sound {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; color: var(--ink-dim);
}
.attach-tile.sound span { font: 400 9.5px/1 var(--mono); letter-spacing: 0.04em; }
/* Behind the clip: a video whose first frame never paints leaves a transparent box,
   and a black square says nothing about what is attached. */
.attach-under {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: var(--ink-mute);
}
.attach-tile video { position: relative; opacity: 0; transition: opacity 160ms ease; }
.attach-tile video.ready { opacity: 1; }
.attach-badge {
  position: absolute; left: 4px; bottom: 4px; display: flex; color: var(--ink-strong);
  filter: drop-shadow(0 1px 2px oklch(0% 0 0 / 0.75));
}
.attach-drop {
  position: absolute; right: 3px; top: 3px; width: 18px; height: 18px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 999px; cursor: pointer;
  color: var(--ink-strong); background: oklch(18% 0.02 285 / 0.72);
}
.attach-drop:hover { background: oklch(12% 0.02 285 / 0.92); }
/* Item 216: the Paid switch, the plain price box beside it, and the line that says in
   words what he will see. No spinners anywhere near a price. */
.attach-lock { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; flex-basis: 100%; }
.attach-price-box { display: inline-flex; align-items: center; gap: 6px; }
.attach-price-box .unit { font: 400 11px/1 var(--mono); letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-dim); }
.attach-price { -moz-appearance: textfield; appearance: textfield; }
.attach-price::-webkit-outer-spin-button, .attach-price::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.attach-state { flex-basis: 100%; font: 400 11.5px/1.5 var(--mono); color: var(--ink-dim); }
.attach-state.warn { color: var(--warn-ink); }
/* The shared form rule wants every input to fill its row; a price is four digits. */
.attach-price { flex: none; width: 110px; height: 36px; }
/* Recording: the panel's warn hue, and a pulse slow enough to read as "still going"
   rather than as an error blinking. */
.icon-button.recording {
  color: var(--warn-ink);
  background: oklch(70% 0.18 25 / 0.18);
  box-shadow: inset 0 0 0 1px oklch(75% 0.16 25 / 0.5);
  animation: throb 1.5s ease-in-out infinite;
}
@keyframes throb { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }

/* The send button on the task thread: a square, tinted by the task's kind. */
.send-square {
  flex: none; width: 46px; height: 46px; border: 0; border-radius: var(--r-control);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  color: var(--ink-faint); background: oklch(100% 0 0 / 0.07);
  box-shadow: inset 0 0 0 1px var(--line);
}
.send-square.armed {
  color: var(--ink-inverse); background: oklch(80% 0.17 var(--hue, 200));
  box-shadow: 0 0 22px -6px oklch(80% 0.17 var(--hue, 200) / 0.9);
}

/* The read-only footer: a conversation her chatter is handling, or an archived task. */
.readonly {
  display: flex; align-items: center; gap: 10px;
  font: 400 11.5px/1.4 var(--mono); letter-spacing: 0.06em; color: oklch(78% 0.02 285);
}

/* --- the customer sheet (brief §3C/§3D) ---------------------------------------
 * "Panel nie powinien zajmować miejsca przez cały czas" — so it is a sheet over the
 * conversation, not a column beside it, and it is closed until asked for. */
.scrim {
  position: absolute; inset: 0; z-index: 5;
  background: oklch(12% 0.03 285 / 0.55);
  backdrop-filter: blur(2px);
  animation: fade 180ms ease;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.sheet {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 6;
  max-height: 82%; display: flex; flex-direction: column;
  border-radius: 22px 22px 0 0;
  /* Opaque, and no backdrop blur of its own: the scrim already blurs what is behind,
     and a second `backdrop-filter` nested inside the frame's own left the conversation
     legible straight through the sheet — found by opening it. */
  background: oklch(24% 0.035 285);
  box-shadow: 0 -1px 0 oklch(100% 0 0 / 0.16), 0 -30px 60px -20px oklch(8% 0.06 285 / 0.9);
  animation: rise 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes rise { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes drawer { from { transform: translateX(28px); opacity: 0; } to { transform: none; opacity: 1; } }
/* A flex item will not shrink below its content's widest unbreakable line, and the
   gallery's tabs — "ALL 19 · BOUGHT 1 · NEW TO HIM 18" — are one: with two-digit
   counts they pushed the sheet past its 340px and took the difference out of the
   conversation. The tabs scroll inside the sheet instead of widening it. */
.sheet { min-width: 0; }
.sheet .segment { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
.sheet .segment::-webkit-scrollbar { display: none; }
.sheet .segment .chip { flex: none; }
.sheet-grip {
  flex: none; width: 40px; height: 4px; border-radius: 999px; margin: 10px auto 2px;
  background: oklch(100% 0 0 / 0.22);
}
.sheet-head {
  flex: none; display: flex; align-items: center; gap: 12px; padding: 8px 18px 10px;
}
.sheet-head .screen-title { flex: 1; }
.sheet-body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 16px; padding: 4px 18px 20px;
}
.kv { display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--line-soft);
  font: 400 12px/1.4 var(--mono); letter-spacing: 0.04em; color: var(--ink-mute); }
.kv:last-child { border-bottom: 0; }
/* Transactions (2026-09-28): who and what on the left, the amount on the right. */
.tx-list { display: flex; flex-direction: column; margin-top: 10px; }
.tx { display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--line-soft); }
.tx:last-child { border-bottom: 0; }
.tx-who { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.tx-who b { font: 600 14px/1.25 var(--sans); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tx-who span { font: 400 11.5px/1.3 var(--mono); letter-spacing: 0.03em; color: var(--ink-mute); }
.tx-amount { flex: none; font: 600 14px/1.2 var(--mono); color: var(--ink);
  font-variant-numeric: tabular-nums; }
.kv b { font: 600 14px/1.2 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
/* A payout that reached her (2026-10-13): green, the one colour on the list that means
 * "this money is yours now". Payments and payouts still on their way keep the plain row. */
.tx.paid { margin: 2px 0; padding: 10px 12px; border-bottom: 0; border-radius: 12px;
  background: oklch(84% 0.19 145 / 0.12); box-shadow: inset 3px 0 0 var(--live); }
.tx.paid .tx-who b, .tx.paid .tx-amount { color: var(--live); }
.tx.paid .tx-who span { color: oklch(84% 0.12 145); }

.tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.tiles .tile { position: relative; aspect-ratio: 1; border-radius: 12px; overflow: hidden;
  background: var(--raise); cursor: pointer; border: 0; padding: 0;
  box-shadow: inset 0 0 0 1px var(--line); }
.tiles .tile img, .tiles .tile video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The file's name over its own picture (the ADD screen's picked files): what the
   camera called it, small, and only there so two similar shots can be told apart. */
.tiles .tile .name {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 14px 6px 5px;
  font: 500 9.5px/1.2 var(--mono); color: oklch(96% 0.01 285); text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background: linear-gradient(to top, oklch(12% 0.03 285 / 0.85), transparent);
}
/* A picked file on the ADD screen: the picture is not a control, the × is. Clicking a
   thumbnail used to remove it — a destructive action on the largest target there is. */
.tiles .tile.picked { cursor: default; }
/* A picked master's "saved" chip sits top-left, not on its name at the bottom (item 125,
   QA F-39: it covered "…ress"); under the clip mark when there is one. */
.tiles .tile.picked .seen { bottom: auto; top: 5px; }
.tiles .tile.picked .clip-mark ~ .seen { top: 32px; }
.view-scroll.stable-gutter { scrollbar-gutter: stable; }
.note-line.done-line { font-size: 13px; color: var(--on-ink); }
.tiles .tile .x {
  position: absolute; top: 5px; right: 5px; width: 24px; height: 24px;
  display: grid; place-items: center; border: 0; border-radius: 50%; padding: 0;
  cursor: pointer; color: oklch(97% 0.01 285); background: oklch(14% 0.03 285 / 0.72);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.18);
}
.tiles .tile .x:hover { background: oklch(58% 0.2 25 / 0.92); }
/* Roadmap 81: a clip in the grid. The poster is the tile; this says it moves. Any
   picture of a clip carries it since 84 — a task's material, a master's preview. */
.clip-mark {
  position: absolute; right: 5px; top: 5px;
  display: grid; place-items: center; width: 22px; height: 22px;
  border-radius: 50%; color: oklch(97% 0.01 285);
  background: oklch(14% 0.03 285 / 0.68);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.2);
}
/* A picked file's × owns that corner, so its clip mark takes the other. */
.tiles .tile.picked .clip-mark { right: auto; left: 5px; }
/* Roadmap 85: send this master in the conversation. Bottom right, clear of the clip
   mark (top right) and the "sent" marker (bottom left). */
.tiles .tile .tile-send {
  position: absolute; right: 5px; bottom: 5px;
  display: grid; place-items: center; width: 28px; height: 28px;
  padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  color: var(--ink-inverse); background: oklch(96% 0.01 285 / 0.92);
  box-shadow: 0 4px 12px -4px oklch(8% 0.06 285 / 0.8);
}
.tiles .tile .tile-send:hover { background: oklch(100% 0 0); }
.tiles .tile .tile-send:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* 0b (2026-09-30): what is in the post, in the archive beside the form and in the
   picker sheet — a ring in the "saved" green and its own label, so a tap visibly did
   something. The eye (bottom right, where a send sits elsewhere) opens its history. */
.tiles .tile.chosen { box-shadow: inset 0 0 0 3px oklch(84% 0.19 165); }
.tiles .tile.chosen img, .tiles .tile.chosen video { opacity: 0.86; }
.tiles .tile[role="button"] { cursor: pointer; }
.tiles .tile[role="button"]:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.tiles .tile .tile-info {
  position: absolute; right: 5px; bottom: 5px;
  display: grid; place-items: center; width: 26px; height: 26px;
  padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  color: oklch(97% 0.01 285); background: oklch(14% 0.03 285 / 0.72);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.18);
}
.tiles .tile .tile-info:hover { background: oklch(14% 0.03 285 / 0.92); }
.tiles .tile .tile-info:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.archive-hint { margin: 0 0 10px; }
/* The picker sheet's own foot: how many are chosen, and the one button that adds them. */
.sheet-foot {
  flex: none; display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 18px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line-soft);
}
.sheet-foot .go { flex: none; width: auto; min-width: 150px; padding: 0 18px; }
.sheet-count { font: 500 12px/1.3 var(--mono); letter-spacing: 0.04em; color: var(--ink-mute); }

/* What the tab above actually holds, since "New to him" is her whole library minus
   his purchases and reads as noise without a sentence. */
/* Kamil, 2026-10-07: the sentence sat on the tabs' heels — a step down from them. */
.note-line.what { margin: 8px 0 10px; }
.tiles .tile .seen {
  position: absolute; left: 5px; bottom: 5px;
  padding: 3px 6px; border-radius: 6px;
  font: 600 9px/1 var(--mono); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-inverse); background: oklch(84% 0.19 165 / 0.92);
}
/* item 209d-ii: the portal's watermarked copy — a warning's colour, not a tick's */
.tiles .tile .seen.watermarked { background: oklch(80% 0.17 70 / 0.94); }

/* D-1 (item 111): a rejected master still held back. Top left, clear of the clip mark
   (top right), the send button (bottom right) and "sent" (bottom left); the picture
   dims, so a glance across the grid finds it before the word is read. */
/* Item 211: bought or not, said on the tile in two colours nobody reads as each other —
   gold for what he paid for, the dark badge for what he has not. */
.tiles .tile .status-mark, .tiles .tile .sent-mark {
  position: absolute; left: 5px; padding: 3px 5px; border-radius: 6px; white-space: nowrap;
  font: 600 8px/1 var(--mono); letter-spacing: 0.05em; text-transform: uppercase;
}
/* The status sits top-left, beside the kind mark's corner, where a ~100 px tile has
   room for "NOT BOUGHT"; the bottom-left is the send arrow's row, so only the short
   "Sent" goes there. A rejected file's mark owns the top-left — the status steps down. */
.tiles .tile .status-mark { top: 5px; max-width: calc(100% - 36px); overflow: hidden; text-overflow: ellipsis; }
.tiles .tile .held-mark ~ .status-mark { top: 26px; }
.tiles .tile .sent-mark { bottom: 5px; color: oklch(88% 0.1 220); background: oklch(30% 0.08 220 / 0.9); }
.tiles .tile .status-mark.bought { color: oklch(24% 0.05 55); background: oklch(82% 0.16 55 / 0.96); }
.tiles .tile .status-mark.unbought {
  color: oklch(90% 0.01 285); background: oklch(14% 0.03 285 / 0.72);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.18);
}
.tiles .tile .held-mark {
  position: absolute; left: 5px; top: 5px;
  padding: 3px 6px; border-radius: 6px;
  font: 600 9px/1 var(--mono); letter-spacing: 0.08em; text-transform: uppercase;
  color: oklch(97% 0.01 25); background: oklch(52% 0.18 25 / 0.94);
}
.tiles .tile.held img, .tiles .tile.held video { opacity: 0.5; }
.tiles .tile.held:disabled { cursor: not-allowed; }

/* --- ADD (§F) ------------------------------------------------------------------ */

/* The purpose tiles: the prototype's two-column type picker. Several may be on at
   once — §F says so, and one good photograph is a feed post and a story and an ad. */
.type-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.type-tile {
  display: flex; align-items: center; gap: 11px; min-height: 60px; padding: 12px 14px;
  border: 0; border-radius: var(--r-tile); cursor: pointer; text-align: left;
  background: oklch(100% 0 0 / 0.05); color: oklch(84% 0.02 285);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.1);
  font: 600 14px/1.2 var(--sans); letter-spacing: -0.005em;
  transition: background 180ms ease, box-shadow 180ms ease;
}
.type-tile:hover { background: var(--raise-3); }
.type-tile[aria-checked="true"], .type-tile[aria-pressed="true"] {
  background: var(--raise-3); color: var(--ink-strong);
  box-shadow: inset 0 0 0 1px oklch(80% 0.17 var(--hue, 200) / 0.6),
              0 0 22px -8px oklch(80% 0.17 var(--hue, 200) / 0.9);
}
/* The spec writes these in lower case ("post / feed", "reel / video") and the design
   sets them like the names beside them, so the capital is a display decision and the
   value on the wire stays the spec's word. */
.type-tile > span:last-child { text-transform: capitalize; }
.type-icon {
  flex: none; width: 30px; height: 30px; border-radius: var(--r-chip);
  display: flex; align-items: center; justify-content: center;
  background: oklch(80% 0.17 var(--hue, 200) / 0.14); color: oklch(90% 0.13 var(--hue, 200));
}
.type-tile[aria-checked="true"] .type-icon, .type-tile[aria-pressed="true"] .type-icon {
  background: oklch(80% 0.17 var(--hue, 200) / 0.22);
}

/* Folders in the Vault (ROADMAP 246): a row of chips over the purposes, the open
   folder's own line, a name asked in place, and what to do with ticked files. */
.folder-bar { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.folder-chips { align-items: center; }
.folder-bar > .add-files { align-self: flex-start; }
.folder-word {
  display: inline-flex; align-items: center; gap: 5px; margin-right: 2px;
  font: 500 10.5px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-faint);
}
.chip.folder[aria-pressed="true"] { box-shadow: inset 0 0 0 1.5px oklch(80% 0.15 70 / 0.8); }
.chip.danger { color: var(--warn-ink); }
.folder-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.folder-tools b { font: 600 14px/1.2 var(--sans); color: var(--ink-strong); }
.folder-tools > span { font: 500 12px/1.2 var(--sans); color: var(--ink-faint); margin-right: auto; }
.folder-name { display: flex; flex-direction: column; gap: 6px; }
.selection-bar {
  position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px; padding: 8px 10px; margin: 0 0 8px; border-radius: var(--r-control);
  background: oklch(22% 0.03 285 / 0.96); box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.12);
}
.selection-bar select { width: auto; min-width: 150px; }
.tiles .folder-send { grid-column: 1 / -1; justify-self: start; }
/* the composer's queue: what goes after the attached one, one message each */
.attach-queue { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; width: 100%; }
.queue-word { font: 500 12px/1.2 var(--sans); color: var(--ink-faint); }
.queue-strip { display: flex; gap: 4px; align-items: center; }
.queue-tile {
  position: relative; width: 30px; height: 30px; border-radius: 7px; overflow: hidden; flex: none;
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.12);
}
.queue-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.queue-more { font: 600 12px/1 var(--sans); color: var(--ink-soft); }
.chip.send-all { white-space: nowrap; }

/* A package's publications (ROADMAP 245): one line each, what it is and what it is made
   of; her own words and files under the ones that have them. */
.package-list { display: flex; flex-direction: column; gap: 4px; }
.package-part {
  display: flex; flex-direction: column; gap: 4px; padding: 9px 12px;
  border-radius: var(--r-control); background: oklch(100% 0 0 / 0.04);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.08);
}
.package-view .package-part { margin-top: 6px; }
.package-part b { font: 600 13.5px/1.3 var(--sans); color: var(--ink-strong); }
.package-part > span { font: 500 12px/1.3 var(--sans); color: var(--ink-faint); }
.package-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.package-own {
  display: flex; flex-direction: column; gap: 8px; margin-top: 14px; padding-top: 12px;
  border-top: 1px solid oklch(100% 0 0 / 0.08);
}
.package-form > input, .package-form > textarea { margin-top: 6px; }
.package-form .package-step { margin-top: 18px; color: var(--ink-strong); }
.package-form .package-video { color: oklch(85% 0.14 70); font-weight: 600; }

/* Which service ADD publishes to (item 244): the kind tiles' shape, the service's name
   large and the account quieter under it; the line above the button repeats it. */
.where-pick { display: flex; flex-direction: column; gap: 6px; }
.where-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.where-pick.invalid .where-chips { outline: 1px solid var(--warn); outline-offset: 4px; border-radius: var(--r-chip); }
.where-chip {
  display: inline-flex; align-items: center; gap: 6px; min-height: 34px; padding: 6px 13px;
  max-width: 100%; border: 0; border-radius: 999px; cursor: pointer;
  background: oklch(100% 0 0 / 0.05); color: var(--ink-soft);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.12);
  font: 600 13px/1.2 var(--sans); transition: background 160ms ease, box-shadow 160ms ease;
}
.where-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.where-chip:hover { background: var(--raise-3); color: var(--ink-strong); }
.where-chip[aria-checked="true"] {
  background: oklch(80% 0.17 145 / 0.18); color: var(--ink-strong);
  box-shadow: inset 0 0 0 1.5px oklch(80% 0.17 145 / 0.8);
}
.where-chip svg { flex: none; color: oklch(85% 0.17 145); }
.where-line {
  padding: 10px 14px; border-radius: var(--r-tile);
  background: oklch(80% 0.17 145 / 0.1); box-shadow: inset 0 0 0 1px oklch(80% 0.17 145 / 0.35);
  font: 500 14px/1.3 var(--sans); color: var(--ink-strong);
}
.where-line b { font-weight: 700; }
.where-line.unset {
  background: oklch(80% 0.15 70 / 0.1); box-shadow: inset 0 0 0 1px oklch(80% 0.15 70 / 0.4);
}

/* The destination rows: a coloured edge, a checkbox, a name and a note. */
.rows { display: flex; flex-direction: column; gap: 6px; }
.pick {
  position: relative; display: flex; align-items: center; gap: 13px;
  width: 100%; min-height: 58px; padding: 12px 16px; text-align: left;
  border: 0; border-radius: var(--r-tile); cursor: pointer; overflow: hidden;
  background: oklch(100% 0 0 / 0.05); color: var(--ink); font: inherit;
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.1);
  transition: background 180ms ease, box-shadow 180ms ease;
}
.pick:hover { background: var(--raise-3); }
.pick::before {
  content: ""; position: absolute; left: 0; top: 10px; bottom: 10px;
  width: 3px; border-radius: 999px; background: oklch(80% 0.17 var(--hue, 265));
}
.pick[aria-checked="true"] {
  background: oklch(100% 0 0 / 0.1);
  box-shadow: inset 0 0 0 1px oklch(80% 0.17 var(--hue, 265) / 0.55);
}
.box {
  flex: none; width: 26px; height: 26px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: var(--raise); color: transparent;
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.22);
}
.pick[aria-checked="true"] .box {
  background: oklch(80% 0.17 var(--hue, 265)); color: var(--ink-inverse); box-shadow: none;
}
.pick-body { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pick-body b { font: 600 15px/1.2 var(--sans); letter-spacing: -0.005em;
  color: oklch(86% 0.02 285); }
.pick[aria-checked="true"] .pick-body b { color: var(--ink-strong); }
.pick-body span { font: 400 11px/1.3 var(--mono); color: oklch(74% 0.02 285); }

/* The media drop: the prototype's 84px square next to the caption box. */
/* The picker is a column with air in it: its tile grid sat straight under the Files
   button with nothing between them, which read as the tiles covering it. */
.picker { display: flex; flex-direction: column; gap: 8px; }
.compose-row { display: flex; gap: 8px; }
.drop {
  flex: none; width: 84px; height: 84px; border-radius: var(--r-tile);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; border: 0; padding: 0;
  color: oklch(86% 0.02 285); background: oklch(100% 0 0 / 0.05);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.16);
}
.drop:hover { background: var(--raise-3); }
.drop span { font: 400 10px/1 var(--mono); letter-spacing: 0.06em; }
.drop.filled { color: var(--on-ink); box-shadow: inset 0 0 0 1px oklch(84% 0.19 165 / 0.5); }
.compose-row textarea { flex: 1 1 0; width: auto; min-width: 0; height: 84px;
  border-radius: var(--r-tile); }

/* Where a master has been (§F). */
.use { display: flex; flex-direction: column; gap: 3px;
  padding: 10px 0; border-bottom: 1px solid var(--line-soft); }
.use:last-child { border-bottom: 0; }
.use .head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font: 400 14px/1.4 var(--sans); }
.use .head b { font-weight: 600; }
.use .when { font: 400 11px/1.4 var(--mono); color: var(--ink-faint); }

/* --- OPTIONS (§G) -------------------------------------------------------------- */

.option {
  display: flex; align-items: center; gap: 13px; width: 100%;
  min-height: 60px; padding: 12px 16px; text-align: left;
  border: 0; border-radius: var(--r-tile); cursor: pointer;
  background: var(--raise-2); color: var(--ink); font: inherit;
  box-shadow: inset 0 0 0 1px var(--line);
}
.option:hover { background: oklch(100% 0 0 / 0.13); }
.option-icon {
  flex: none; width: 32px; height: 32px; border-radius: var(--r-chip);
  display: flex; align-items: center; justify-content: center;
  color: oklch(88% 0.14 var(--hue, 165)); background: oklch(84% 0.17 var(--hue, 165) / 0.16);
  box-shadow: inset 0 0 0 1px oklch(84% 0.17 var(--hue, 165) / 0.4);
}
.option-body { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.option-body b { font: 600 15px/1.2 var(--sans); }
.option-body span { font: 400 11.5px/1.35 var(--mono); color: var(--ink-dim); }
/* POSTED (2026-10-13): which service a thing is on, as a tag in that service's colour. */
.option-body .service-tag {
  align-self: flex-start; max-width: 100%; margin-bottom: 2px; padding: 1px 8px;
  border-radius: 999px; font: 500 10.5px/1.5 var(--mono); letter-spacing: 0.04em;
  text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: oklch(88% 0.1 var(--svc-hue)); background: oklch(60% 0.12 var(--svc-hue) / 0.22);
  box-shadow: inset 0 0 0 1px oklch(75% 0.12 var(--svc-hue) / 0.35);
}
.option .chev { flex: none; color: var(--ink-mute); }

/* One connected service's earnings. */
.service { padding: 14px 0; border-bottom: 1px solid var(--line-soft); }
.service:last-of-type { border-bottom: 0; }
.service-head { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }
.service-head b { font: 600 15px/1.2 var(--sans); }
/* Item 234: one line — the state dot and the name on the left, the buttons on the right. */
.service-head { align-items: center; flex-wrap: wrap; }
.service-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.service-as { font: 400 11.5px/1.4 var(--mono); color: var(--ink-dim); white-space: nowrap; }
/* Item 256: the hub's own question when the portal could not tell it was her. */
.connect-ask .detail-block { margin-top: 10px; }
.connect-ask input[type="email"], .connect-ask input[type="password"] { width: 100%; }
.connect-ask .note-line a { color: inherit; text-decoration: underline; }
.connect-ask .go-chip { align-self: flex-start; }
.service-head .service-actions { flex: 1 1 auto; justify-content: flex-end; margin: 0; }
/* A phone has no room for one line: the buttons go under the name. */
@media (max-width: 700px) {
  .service-head .service-actions { flex-basis: 100%; justify-content: flex-start; }
}
/* The state as a dot (2026-10-13): green connected, red needing a key or disconnected,
   grey otherwise. Its words on hover, or beside it after a tap. The button is larger
   than the dot so a finger finds it. */
.service-head .state { display: inline-flex; align-items: center; gap: 6px; }
.state-dot {
  flex: none; width: 22px; height: 22px; padding: 0; border: 0; background: none;
  cursor: pointer; display: inline-grid; place-items: center;
}
.state-dot::before {
  content: ""; width: 11px; height: 11px; border-radius: 50%; background: var(--ink-faint);
}
.state-dot.on::before { background: var(--live); box-shadow: 0 0 8px oklch(84% 0.19 145 / 0.7); }
/* Item 255: a service's one button — Connect, or Reconnect for a key that stopped
   working — outlined in the accent so it reads as the thing to press. */
.chip.go-chip { color: var(--ink-strong); box-shadow: inset 0 0 0 1px var(--accent); }
.note-line.ok { color: var(--live); }
.state-dot.off::before { background: var(--warn); box-shadow: 0 0 8px oklch(72% 0.19 25 / 0.7); }
.service-head .state-said { font: 500 11px/1.2 var(--mono); letter-spacing: 0.04em; color: var(--ink-dim); }
.service-head span { font: 600 14px/1 var(--mono); color: var(--on-ink);
  font-variant-numeric: tabular-nums; }
a.chip { text-decoration: none; }

/* The notification switches. */
.notif { display: flex; align-items: center; gap: 12px; padding: 10px 0;
  border-bottom: 1px solid var(--line-soft); }
.notif:last-child { border-bottom: 0; }
.notif-body { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.notif-body b { font: 600 14.5px/1.2 var(--sans); overflow-wrap: anywhere; }
.notif-body span { font: 400 11px/1.4 var(--mono); color: var(--ink-faint); }

/* --- sign in -------------------------------------------------------------------
 * Not a screen the prototype draws. Built out of the same parts: the frame's own type
 * scale, the gradient action, and nothing else. Before anyone is signed in the frame
 * *is* this card — there is no money to show, no tab to sit on and no list to put
 * beside it, so the panel's chrome would be four empty shapes around two fields. */
.frame[data-view="signin"] {
  width: min(400px, 100%);
  height: auto;
  min-height: 0;
  align-self: center;
}
.frame[data-view="signin"] > .topbar,
.frame[data-view="signin"] > .rule,
.frame[data-view="signin"] > .footer { display: none; }
.frame[data-view="signin"] > .middle { flex: none; }

.signin { flex: 1; display: flex; flex-direction: column; justify-content: center; }
.signin-card {
  display: flex; flex-direction: column; gap: 22px;
  padding: 34px 28px 30px;
}
.signin-head { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.signin-mark {
  font: 600 20px/1 var(--mono); letter-spacing: 0.34em; text-transform: uppercase;
  color: var(--ink-strong); padding-left: 0.34em;
}
.signin-sub {
  font: 400 11px/1 var(--mono); letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-mute);
}
.signin-fields { display: flex; flex-direction: column; gap: 8px; }
.signin .note-line { text-align: center; }
/* A refusal has to look unlike the help it replaces — they were the same size and the
   same grey, so a wrong password read as the line that had always been there. */
.signin .note-line.err { color: var(--warn-ink); }
input.bad, input.bad:focus { box-shadow: inset 0 0 0 1px oklch(75% 0.16 25 / 0.7); }

/* The same card, when the person signed in belongs on another page. */
.frame[data-view="signin"] { width: min(400px, 100%); }
#elsewhere .signin-card { gap: 18px; }
#elsewhere .rows { gap: 8px; }

/* --- the footer / the rail (§A) ------------------------------------------------ */

.footer {
  flex: none;
  display: flex;
  gap: 6px;
  padding: 10px;
  /* The home indicator on an iPhone sits over the last 34px; without this the
   * rightmost tab is half under it. */
  padding-bottom: calc(10px + env(safe-area-inset-bottom));
  background: oklch(100% 0 0 / 0.05);
  box-shadow: inset 0 1px 0 var(--line);
}
.tab {
  position: relative;
  flex: 1;
  min-height: 58px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px;
  border: 0; border-radius: 14px;
  background: transparent; color: var(--ink-dim);
  cursor: pointer;
  transition: background 180ms ease, color 180ms ease, box-shadow 180ms ease;
}
.tab:hover { background: oklch(100% 0 0 / 0.1); }
.tab[aria-selected="true"] {
  background: var(--raise-hover);
  color: var(--ink-strong);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.2),
              0 0 22px -6px oklch(80% 0.18 210 / 0.55);
}
.tab span {
  font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase;
  font-family: var(--mono); font-weight: 400;
}
.tab[aria-selected="true"] span { font-weight: 600; }

/* The two CHAT counters: two colours, so fan traffic and internal traffic are told
 * apart without reading the number (50c decides what goes in them). */
/* On the phone's footer each badge sits on the top-right corner of its tab's icon, the
 * way a phone's own badges do — not pinned to the tab's edge, where two of them (CHAT)
 * grew leftwards over the icon and into the next tab (Michal, 2026-10-19: "tragicznie
 * przesunięte"). Two stack rather than queue, so a narrow phone's tab still holds them;
 * the number alone, without its glyph, keeps each one small. The rail (≥ wide) puts them
 * back in line after the label, below. */
.badges {
  position: absolute; top: 3px; left: calc(50% + 7px); right: auto;
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  pointer-events: none;
}
.footer .badge { height: 15px; padding: 0 5px; font-size: 9px; }
.footer .badge svg { display: none; }
.badge {
  display: flex; align-items: center; gap: 2px;
  height: 17px; padding: 0 5px 0 4px; border-radius: 999px;
  color: var(--ink-inverse);
  font: 600 9.5px/1 var(--mono);
}
.badge.fans { background: var(--on); box-shadow: 0 0 12px -2px oklch(84% 0.19 165 / 0.9); }
.badge.team { background: var(--team); box-shadow: 0 0 12px -2px oklch(80% 0.17 300 / 0.9); }
/* And the questions asked on her tasks, on TO DO — its own colour, because it is its
   own kind of waiting: somebody is asking about work, not writing to her. */
.badge.tasks { background: oklch(82% 0.16 55); box-shadow: 0 0 12px -2px oklch(82% 0.16 55 / 0.9); }
/* The same question, said on the card it belongs to. */
.pill.asking { color: oklch(24% 0.05 55); background: oklch(82% 0.16 55); }
/* Item 132: work she has not looked at yet — her own hue, told apart from the questions
   beside it (orange) and from an ending (green), because it is neither. */
.badge.new { background: var(--team); box-shadow: 0 0 12px -2px oklch(80% 0.17 300 / 0.9); }
/* The same, said on the card that is new to her. */
.pill.fresh { color: oklch(22% 0.05 300); background: var(--team); }
/* Item 113b: a task of his ended — green like the accepted mark, apart from questions. */
.badge.closed { background: oklch(84% 0.19 165); box-shadow: 0 0 12px -2px oklch(84% 0.19 165 / 0.9); }
.pill.news { color: oklch(20% 0.04 165); background: oklch(84% 0.19 165); }
#todo-archive { position: relative; }
#todo-archive.news::after {
  content: ""; position: absolute; top: 3px; right: 3px; width: 8px; height: 8px;
  border-radius: 50%; background: oklch(84% 0.19 165);
  box-shadow: 0 0 8px oklch(84% 0.19 165 / 0.9);
}

/* --- the desktop ---------------------------------------------------------------
 * The design has no desktop artboard, so this is the same frame opened out, not a
 * second design: the footer becomes a left rail (the four sections keep their order
 * and their icons), the money bar keeps the top, and the middle splits into the two
 * panes a phone shows one at a time. Nothing new is introduced — a wide screen only
 * stops hiding what a narrow one had to. */

@media (min-width: 1024px) {
  body { padding: min(4vh, 32px) min(3vw, 32px); }

  .frame {
    width: min(1280px, 100%);
    min-height: 0;
    display: grid;
    grid-template-columns: 236px minmax(0, 1fr);
    /* topbar, rule, the item-108 notice (an empty row while it is hidden), the panel */
    grid-template-rows: auto auto auto minmax(0, 1fr);
    border-radius: 26px;
  }
  /* The sign-in card keeps its size on a wide screen: it is the same card, centred,
     not the panel's grid with one cell filled. */
  .frame[data-view="signin"] {
    display: flex; width: min(400px, 100%); height: auto; grid-template-columns: none;
  }
  .topbar { grid-column: 1 / -1; padding: 20px 26px 16px; }
  .brand { display: flex; }
  .rule { grid-column: 1 / -1; }

  /* The rail. Same nodes as the phone's footer, stood on end. */
  .footer {
    grid-column: 1 / 2; grid-row: 4 / 5;
    flex-direction: column; gap: 4px;
    padding: 14px 12px calc(14px + env(safe-area-inset-bottom));
    background: oklch(100% 0 0 / 0.03);
    box-shadow: inset -1px 0 0 var(--line);
    overflow-y: auto;
  }
  .tab {
    flex: none; min-height: 46px;
    flex-direction: row; justify-content: flex-start; gap: 12px;
    padding: 0 14px; border-radius: 12px;
  }
  .tab span { font-size: 11px; letter-spacing: 0.12em; }
  .badges { position: static; margin-left: auto; flex-direction: row; align-items: center; gap: 3px; }
  .footer .badge { height: 17px; padding: 0 5px 0 4px; font-size: 9.5px; }
  .footer .badge svg { display: inline; }

  .middle { grid-column: 2 / 3; grid-row: 4 / 5; }
  .panel-notice { grid-column: 1 / -1; grid-row: 3 / 4; margin: 10px 26px 0; }
  .panes { display: flex; }
  /* Both panes at once: a list you keep your place in, and the thing you opened. */
  .panes[data-detail="0"] .pane-detail,
  .panes[data-detail="1"] .pane-list { display: flex; }
  .pane-list { flex: 0 0 clamp(320px, 34%, 400px); box-shadow: inset -1px 0 0 var(--line); }
  .pane-detail { flex: 1 1 auto; }
  .view-scroll { padding: 26px 22px 24px; }
  .view-head, .convo-head { padding: 20px 22px 12px; }
  .view-foot { padding: 16px 22px 18px; }
  .log-lines { padding: 4px 22px 14px; }
  .context { margin: 0 22px 14px; }
  .tiles { grid-template-columns: repeat(4, 1fr); }
  /* except in the sheet, which is 340px wide — four across there is a contact sheet */
  .sheet .tiles { grid-template-columns: repeat(3, 1fr); }

  /* The reading column. A detail pane is as wide as the window allows, and letting the
     screen fill it would give a two-field composer 760px and a primary button 700 — at
     which point it is a banner, not a button. So the *content* keeps a column and the
     pane keeps the air; the foot bar still spans the pane, because it is a bar. */
  .pane-detail { --col: 680px; }
  .pane-detail > .view > .view-head,
  .pane-detail > .view > .view-scroll,
  .pane-detail > .view > .log-lines,
  .pane-detail > .view > .convo-head,
  .pane-detail > .view > .view-foot > * {
    width: 100%; max-width: var(--col); margin-inline: auto;
  }
  .pane-detail > .view > .context {
    width: 100%; max-width: calc(var(--col) - 44px); margin-inline: auto;
  }
  .bubble { max-width: 78%; }

  /* A primary action is a button, not a bar. On a phone it spans the frame because
     that is the whole screen and the thumb is the pointer; on a desktop it takes the
     width of its own words and sits where the eye ends up — on the right. */
  .pane-detail .actions { justify-content: flex-end; }
  /* `width: auto` as well as the flex basis: the shared form rule sets `width: 100%`
     on `button.go`, and a flex item told not to grow still takes the width it was
     given — which left "Mark completed" 690px wide. */
  .pane-detail .actions .go { flex: 0 0 auto; width: auto; min-width: 210px; }
  .pane-detail .composer .go { flex: 0 0 auto; width: auto; min-width: 150px; margin-left: auto; }

  /* The sheet stops being a *bottom* sheet on a desktop, and between here and 1500px
     it is a drawer over the conversation, from the right. It used to become a third
     column at every desktop width, and a third column only fits where there is room
     for it: measured with the sheet open, the conversation beside it was 314px at a
     1290px window, 140px at 1100 and 67px at 1024 — a composer 28px wide and "reopen
     the conversation" set one word per line (Kamil, 2026-09-10: "have you tested it on
     different screen widths?"). A drawer covers the conversation instead of crushing
     it, and the scrim is what closes it. The column comes back at 1500px, below. */
  .sheet {
    top: 0; left: auto; right: 0; bottom: 0;
    width: min(380px, 100%); max-height: none;
    border-radius: 22px 0 0 22px;
    box-shadow: -1px 0 0 oklch(100% 0 0 / 0.16), -30px 0 60px -20px oklch(8% 0.06 285 / 0.9);
    animation: drawer 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  .sheet-grip { display: none; }
  .sheet-head { padding: 20px 18px 10px; }

  /* The archive picker stays a sheet at every width (roadmap 69): it interrupts a form
     to answer one question and hands the form straight back, where the customer panel
     is a column that stays open beside the conversation. */
  .scrim.over { display: block; }
  .sheet.over {
    position: absolute; left: 0; right: 0; bottom: 0; max-height: 82%; flex: 0 0 auto;
    top: auto; width: auto;
    border-radius: 22px 22px 0 0; background: oklch(24% 0.035 285);
    box-shadow: 0 -1px 0 oklch(100% 0 0 / 0.16), 0 -30px 60px -20px oklch(8% 0.06 285 / 0.9);
    animation: rise 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  .sheet.over .sheet-grip { display: block; }
}

/* A very wide window gets a third column of air rather than a 1600px line length. */
@media (min-width: 1500px) {
  .frame { width: min(1440px, 100%); }
  .frame[data-view="signin"] { width: min(400px, 100%); }
  .pane-list { flex: 0 0 380px; }

  /* And only here does the customer sheet become a column beside the conversation,
     which is where a "who am I talking to" panel belongs when it fits: the frame is
     1440 wide, so the rail, a 380px list and a 340px sheet still leave the
     conversation 484px — measured, and the narrowest this layout is allowed. */
  .scrim { display: none; }
  .scrim.over { display: block; }
  .sheet {
    position: static; top: auto; width: auto; max-height: none; flex: 0 0 340px;
    border-radius: 0; animation: none;
    background: oklch(100% 0 0 / 0.03);
    box-shadow: inset 1px 0 0 var(--line);
  }
  .pane-detail.with-sheet { flex-direction: row; }
  .pane-detail.with-sheet > .view { flex: 1 1 auto; min-width: 0; }
}

/* roadmap 74: what the portal said when it refused a send, under the composer */
.refusal { margin: 6px 0 0; font-size: 12.5px; line-height: 1.35; color: #b3261e; }
.refusal.hidden { display: none; }

/* roadmap 76: the messenger's state on a connection row, and the portal's own lock */
.import-line { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 6px; }
.portal-locked { position: relative; display: inline-block; }
.portal-locked .locked-label { position: absolute; left: 8px; bottom: 8px; font-size: 12px; padding: 2px 8px; border-radius: 999px; background: rgba(0,0,0,.55); color: #fff; }


/* --- the lightbox (84) -----------------------------------------------------------
 * One for the whole panel — a gallery tile, a bubble, a task's material all open it.
 * Fixed over everything, the picture as large as the screen allows and never larger
 * than itself; the page behind does not scroll while it is open. */
.viewable { cursor: zoom-in; }
.viewable:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
html.lightbox-open, html.lightbox-open body { overflow: hidden; }
.lightbox {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: oklch(8% 0.03 285 / 0.94);
  animation: fade 160ms ease;
  touch-action: pan-y pinch-zoom;
}
.lightbox-stage {
  position: absolute; inset: 56px 64px;
  display: flex; align-items: center; justify-content: center;
}
.lightbox-media {
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  object-fit: contain; border-radius: 8px; display: block;
  box-shadow: 0 30px 80px -30px oklch(0% 0 0 / 0.9);
  background: oklch(100% 0 0 / 0.04);
}
.lightbox .icon-button {
  position: absolute; color: oklch(96% 0.01 285); background: oklch(100% 0 0 / 0.1);
}
.lightbox .icon-button:hover { background: oklch(100% 0 0 / 0.2); }
.lightbox-close { top: 10px; right: 10px; }
.lightbox-step { top: 50%; transform: translateY(-50%); width: 44px; height: 44px; border-radius: 50%; }
.lightbox-step.prev { left: 10px; }
/* `hidden` on one picture (QA F-36): `.icon-button { display: flex }` outranks the
   browser's own rule for the attribute, so the arrows showed with nothing to step to. */
.lightbox-step[hidden], .lightbox-count[hidden] { display: none; }
/* Item 126a (QA F-32): what POSTED shows of what is published. */
.notif .published-thumb { flex: none; width: 48px; height: 48px; border-radius: 10px;
  object-fit: cover; margin-right: 10px; background: oklch(100% 0 0 / 0.08); }
.poll-sides { display: flex; gap: 10px; flex-wrap: wrap; }
.poll-side { margin: 0; flex: 1 1 120px; max-width: 200px; }
/* `height: auto` is the whole of QA F-47 (item 134), and it is not decoration.
   `aspect-ratio` governs a box only while one of its dimensions is auto, and item 116
   put real `width`/`height` attributes on every thumbnail so its box is right before a
   byte arrives — so a 640x640 file drew 200 wide and **1200 tall**, with the votes a
   screenful below the pictures. Every other sized <img> on this page already says a
   height (`100%` inside a fixed box, `auto` in the conversation); this one did not. */
.poll-side img {
  width: 100%; height: auto; aspect-ratio: 1; object-fit: cover;
  border-radius: 12px; display: block;
}
.poll-side figcaption { margin-top: 6px; font: 400 12px/1.3 var(--mono); color: var(--ink-dim); }
.published-poster { position: relative; width: 160px; max-width: 100%; aspect-ratio: 9 / 16;
  border-radius: 12px; overflow: hidden; margin: 4px 0 8px; background: oklch(100% 0 0 / 0.08); }
.published-poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
.field-label { margin: 10px 0 4px; font: 400 10px/1 var(--mono); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-mute); }
.lightbox-step.next { right: 10px; }
.lightbox-count {
  position: absolute; bottom: 18px; left: 0; right: 0; text-align: center;
  font: 500 11px/1 var(--mono); letter-spacing: 0.1em; color: oklch(80% 0.02 285);
}
/* A phone (item 122, QA F-24): the frame's margin, the bar's two rows and its padding
   took ~200 px of an 844 px screen before any content. The frame nearly meets the
   edges, the bar is one row, and the pages open from a button (Password and Sign out
   were there too until item 194 moved them into OPTIONS). */
@media (max-width: 560px) {
  body { padding: 6px; }
  .frame { border-radius: 18px; }
  .topbar { padding: 10px 14px 8px; gap: 8px 12px; }
  .surfaces { order: 0; flex-basis: auto; margin-right: auto; position: relative; }
  .surfaces > .surfaces-toggle { display: flex; }
  .surfaces-list { display: none; }
  /* A conversation's composer: the file row and Send share a line, as the box above
     them keeps its own — three rows were ~200 px of a 390 px-wide screen. */
  .view-foot:has(> .attach) { flex-direction: row; flex-wrap: wrap; align-items: center; }
  .view-foot:has(> .attach) > textarea,
  .view-foot:has(> .attach) > .note-line,
  .view-foot:has(> .attach) > .refusal,
  .view-foot:has(> .attach) > .readonly { flex-basis: 100%; }
  .view-foot:has(> .attach) > .attach { flex: 1 1 auto; min-width: 0; }
  .view-foot:has(> .attach) > .composer { flex: none; margin-left: auto; }
  .view-foot:has(> .attach) > .composer .go { width: auto; padding: 0 18px; }
  .surfaces.open .surfaces-list {
    display: flex; flex-direction: column; align-items: stretch; gap: 6px;
    position: absolute; top: calc(100% + 6px); left: 0; z-index: 30; padding: 8px;
    border-radius: 14px; background: oklch(22% 0.03 285 / 0.97);
    box-shadow: 0 12px 30px -10px oklch(8% 0.06 285 / 0.9), inset 0 0 0 1px var(--line);
  }
  /* On a desk page (roles, review, control) the button sits at the header's right
     edge, so the list hangs from its right corner. Anchored at `left: 0` it ran past
     the frame, which clips, and control's menu was five half-words nobody could press
     (Kamil, on a phone, 2026-09-24). Here once, rather than in each page's own style,
     which is where two of the three had it and the third did not. */
  header .surfaces.open .surfaces-list { left: auto; right: 0; }
}

/* A phone: the picture takes the whole width, the arrows sit over its edges, and a
   swipe does the same job. */
@media (max-width: 640px) {
  .lightbox-stage { inset: 52px 0 44px; }
  .lightbox-media { border-radius: 0; }
  .lightbox-step { background: oklch(10% 0.03 285 / 0.55); }
}
@media (prefers-reduced-motion: reduce) {
  .lightbox { animation: none; }
}

/* --- prices (2026-09-10) ----------------------------------------------------------
 * A price is a number of coins and a sentence saying what it means in money; a package
 * is a switch, a length and a discount. Each block the same shape as the rest of
 * OPTIONS' detail screens. */
/* `flex: none`: `.view-scroll` is a flex column that shrinks its children before it
   scrolls, and `overflow-x: auto` takes away the floor a flex item's content gives it —
   with Adoreo's longer list the picker was squashed to a 4px sliver (Kamil, seen live
   2026-09-10; the check before had only looked at a short list). */
.prices-pick { flex: none; margin: 4px 0 6px; align-self: flex-start; max-width: 100%; overflow-x: auto; }
.prices .detail-block { padding: 12px 0; border-bottom: 1px solid var(--line-soft); }
.prices .detail-block:last-child { border-bottom: 0; }
.price-input { display: flex; align-items: center; gap: 10px; }
.price-input input { width: 140px; max-width: 100%; }
.prices .unit { font: 400 11px/1 var(--mono); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-dim); }
.package { display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  gap: 6px 12px; padding: 8px 0; }
.package b { font: 600 14px/1.2 var(--sans); }
.package-off { display: inline-flex; align-items: center; gap: 8px; }
.package-off input { width: 76px; height: 38px; }
.package .note-line { grid-column: 1 / -1; }
/* Shown, not offered: until Adoreo opens price setting, or while a package is off. */
.prices input:disabled, .prices .switch:disabled { opacity: 0.55; cursor: default; }

/* Item 93c: her own password, one dialog for the three pages (`hub.changePassword`).
   A native <dialog>, so the backdrop, the focus and Escape are the browser's. */
dialog.hub-dialog {
  width: min(380px, calc(100% - 32px)); padding: 0; border: 0; border-radius: 20px;
  color: var(--ink); background: oklch(24% 0.035 285);
  box-shadow: 0 0 0 1px oklch(100% 0 0 / 0.14), 0 30px 80px -20px oklch(8% 0.06 285 / 0.9);
}
dialog.hub-dialog::backdrop { background: oklch(12% 0.03 285 / 0.6); backdrop-filter: blur(2px); }
.hub-dialog-body { display: flex; flex-direction: column; gap: 10px; padding: 20px; }
.hub-dialog-title {
  font: 600 12px/1.2 var(--mono); letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-strong); margin-bottom: 4px;
}
.hub-dialog-note { min-height: 1.4em; font: 400 12px/1.45 var(--mono); color: var(--warn-ink); }
.hub-dialog-note.ok { color: var(--on-ink); }
.hub-dialog-actions { display: flex; gap: 8px; justify-content: flex-end; align-items: center; }
.hub-dialog-actions .go { width: auto; flex: 0 0 auto; padding: 0 20px; }

/* The internal chat's way in (2026-09-14): who there is to write to, over the list. */
.write-to { align-items: center; gap: 8px; margin-bottom: 2px; }
.write-to-label {
  font: 400 10.5px/1 var(--mono); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-faint);
}

/* Item 108: a line under the bar when the panel changed under the person using it —
   an admin took a creator away and the panel moved them on. */
.panel-notice {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 8px 16px 0; padding: 8px 8px 8px 14px; border-radius: 12px;
  font: 400 12px/1.5 var(--mono); color: var(--ink);
  background: oklch(80% 0.17 300 / 0.12); box-shadow: inset 0 0 0 1px oklch(80% 0.17 300 / 0.35);
}

/* A phone (item 153c, Phase 10's gap): each row one line — its label, then its chips
   scrolling sideways — so five rows cost five lines, not a screen of wrapped chips. */
@media (max-width: 560px) {
  .task-filters { gap: 6px; }
  .filter-row { flex-direction: row; align-items: center; gap: 8px; }
  .filter-row > .detail-label { flex: none; width: 74px; margin: 0; }
  .filter-row > .chips { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; overflow-x: auto;
    scrollbar-width: none; overscroll-behavior-x: contain; }
  .filter-row > .chips::-webkit-scrollbar { display: none; }
  .filter-row > .chips > .chip { flex: none; }
}

/* --- Earnings (Michal, 2026-10-13) --------------------------------------------------
 * For the creator, not for an admin: what she can take out now in one big number on a
 * green card, what she has earned and been paid side by side under it, then the button
 * and her last payouts. Big figures, small labels, no explanations. */
.view-scroll.earn { display: flex; flex-direction: column; gap: 14px; padding-top: 6px; }
.view-scroll.earn > * { flex: none; }
.earn-label {
  font: 500 11px/1.3 var(--mono); letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-mute);
}
.earn-amount { display: flex; align-items: baseline; gap: 2px; color: var(--ink); }
.earn-units { font: 700 32px/1.05 var(--sans); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.earn-cents { font: 500 14px/1 var(--mono); color: var(--ink-dim); }
.earn-amount.huge .earn-units { font-size: clamp(44px, 9vw, 64px); }
.earn-amount.huge .earn-cents { font-size: 18px; }
.earn-hero {
  display: flex; flex-direction: column; gap: 10px; padding: 22px 22px 24px;
  border-radius: 22px;
  background:
    radial-gradient(120% 140% at 0% 0%, oklch(84% 0.19 145 / 0.32), transparent 60%),
    radial-gradient(120% 140% at 100% 100%, oklch(80% 0.13 200 / 0.28), transparent 60%),
    oklch(100% 0 0 / 0.05);
  box-shadow: inset 0 0 0 1px oklch(84% 0.19 145 / 0.35), 0 18px 40px -24px oklch(84% 0.19 145 / 0.6);
}
.earn-hero .earn-label { color: oklch(90% 0.1 145); }
.earn-hero .earn-units { color: oklch(96% 0.06 145); }
.earn-tiles { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.earn-tile {
  display: flex; flex-direction: column; gap: 8px; padding: 16px 16px 18px; border-radius: 18px;
  background: var(--raise); box-shadow: inset 0 0 0 1px var(--line);
}
.earn-tile.paid .earn-units { color: var(--live); }
.earn-coming { font: 500 11.5px/1.3 var(--mono); color: var(--accent); }
.earn-card {
  display: flex; flex-direction: column; gap: 8px; padding: 14px 16px; border-radius: 18px;
  background: var(--raise); box-shadow: inset 0 0 0 1px var(--line);
}
.earn-ask { display: flex; flex-direction: column; gap: 10px; }
.earn-ask:empty { display: none; }
.earn-ask-line { display: flex; gap: 8px; align-items: stretch; }
.earn-ask-line > input { flex: 1 1 auto; min-width: 0; margin: 0; }
.earn-ask-line > .go { flex: none; width: auto; min-width: 140px; margin: 0; padding: 0 20px; }
.earn-hint { font: 400 11.5px/1.3 var(--mono); color: var(--ink-mute); }
.earn-recent { display: flex; flex-direction: column; gap: 8px; }
.earn-payout {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 11px 14px; border-radius: 14px; background: var(--raise);
  box-shadow: inset 0 0 0 1px var(--line-soft);
}
.earn-payout > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.earn-payout b { font: 600 15px/1.2 var(--sans); font-variant-numeric: tabular-nums; }
.earn-payout span { font: 400 11px/1.3 var(--mono); color: var(--ink-mute); }
.earn-payout .earn-state { flex: none; color: var(--ink-dim); text-transform: uppercase; letter-spacing: 0.06em; }
.earn-payout.paid { background: oklch(84% 0.19 145 / 0.12); box-shadow: inset 3px 0 0 var(--live); }
.earn-payout.paid b, .earn-payout.paid .earn-state { color: var(--live); }
/* …and alive (Michal, same day: "ktoś wchodzący ma się podniecić"): the green card's
 * light drifts slowly, every card lifts and glows under the pointer, the figures count
 * up (in the script) and the bars grow. All of it still for prefers-reduced-motion. */
.earn-hero { position: relative; overflow: hidden; background-size: 180% 180%, 180% 180%, auto;
  animation: earn-drift 9s ease-in-out infinite alternate; }
@keyframes earn-drift {
  from { background-position: 0% 0%, 100% 100%, 0 0; }
  to { background-position: 40% 30%, 60% 70%, 0 0; }
}
.earn-lift { transition: transform 180ms ease, box-shadow 180ms ease, background-color 180ms ease; }
.earn-lift:hover {
  transform: translateY(-2px);
  box-shadow: inset 0 0 0 1px oklch(84% 0.19 145 / 0.5), 0 16px 34px -18px oklch(84% 0.19 145 / 0.55);
}
.earn-hero-top { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; flex-wrap: wrap; }
.earn-hero-top > div { display: flex; flex-direction: column; gap: 10px; }
.earn-withdraw {
  display: inline-flex; align-items: center; gap: 8px; height: 46px; padding: 0 22px;
  border: 0; border-radius: 999px; cursor: pointer;
  font: 700 14px/1 var(--sans); letter-spacing: 0.02em; color: oklch(22% 0.05 160);
  background: linear-gradient(135deg, oklch(88% 0.19 150), oklch(84% 0.14 190));
  box-shadow: 0 10px 26px -10px oklch(84% 0.19 145 / 0.8);
  transition: transform 150ms ease, box-shadow 150ms ease;
}
.earn-withdraw:hover { transform: translateY(-1px) scale(1.03); box-shadow: 0 14px 30px -10px oklch(84% 0.19 145 / 0.95); }
.earn-withdraw:active { transform: scale(0.98); }
.earn-hero .earn-ask { margin-top: 14px; }
.earn-hero .earn-card { background: oklch(20% 0.03 285 / 0.55); }
.earn-period { display: flex; flex-direction: column; gap: 12px; padding: 16px 16px 14px;
  border-radius: 18px; background: var(--raise); box-shadow: inset 0 0 0 1px var(--line); }
.earn-period-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; }
.earn-period-line { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
/* what is under the head: its own height kept while a new period reads */
.earn-period-body { display: flex; flex-direction: column; gap: 6px; min-height: 150px;
  transition: opacity 150ms ease; }
.earn-period-body.reading { opacity: 0.45; }
.earn-period-body .earn-bars { margin-top: 8px; }
.earn-change {
  padding: 3px 9px; border-radius: 999px; font: 600 12px/1.3 var(--mono); cursor: default;
}
.earn-change.up { color: var(--live); background: oklch(84% 0.19 145 / 0.14); }
.earn-change.down { color: var(--warn-ink); background: oklch(72% 0.19 25 / 0.14); }
.earn-bars { display: grid; grid-template-columns: repeat(var(--n), 1fr); gap: 2px; align-items: end; height: 84px; }
.earn-bar {
  height: 0; min-height: 2px; border-radius: 3px 3px 1px 1px; background: oklch(100% 0 0 / 0.1);
  transition: height 700ms cubic-bezier(0.2, 0.8, 0.2, 1), background-color 150ms ease;
}
.earn-bar.some { background: linear-gradient(180deg, oklch(88% 0.19 150), oklch(70% 0.14 190)); }
.earn-bar:hover { background: var(--accent); }
.earn-recent-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.earn-tx { display: flex; flex-direction: column; border-radius: 16px; padding: 2px 14px;
  background: var(--raise); box-shadow: inset 0 0 0 1px var(--line-soft); }
.earn-tx .tx { transition: background-color 150ms ease; }
.earn-tx .tx-amount { color: var(--live); }
.earn-payout { transition: transform 150ms ease; }
.earn-payout:hover { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .earn-hero { animation: none; }
  .earn-lift, .earn-lift:hover, .earn-withdraw, .earn-payout:hover { transition: none; transform: none; }
  .earn-bar { transition: none; }
}
/* The payout account (item 236): what is on file, then the portal form's fields. */
.earn-account .chips { gap: 8px; }
.earn-account-summary { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.earn-account-summary b { font: 600 15px/1.3 var(--sans); }
.earn-account-summary span { font: 400 12px/1.4 var(--mono); color: var(--ink-dim); overflow-wrap: anywhere; }
.earn-account-summary .earn-iban { color: var(--ink); letter-spacing: 0.04em; font-variant-numeric: tabular-nums; }
.earn-form { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; margin-top: 6px; }
.earn-form .earn-field { display: flex; flex-direction: column; min-width: 0; }
.earn-form .earn-field:first-child, .earn-form .earn-field:nth-child(2),
.earn-form .earn-field:nth-child(4) { grid-column: 1 / -1; }
.earn-form .earn-field input { width: 100%; margin: 0; }
.earn-form .earn-field .field-note { margin: 4px 0 2px; }
.earn-form .earn-proof { grid-column: 1 / -1; display: flex; gap: 8px; margin-top: 10px; }
.earn-form .earn-proof input { flex: 1 1 auto; min-width: 0; margin: 0; }
.earn-form .earn-proof input[inputmode="numeric"] { flex: 0 1 180px; }
.earn-form > .field-note, .earn-form > .go { grid-column: 1 / -1; }
.earn-form > .go { margin-top: 8px; }
.earn-form > .go.danger { background: var(--warn-ink); }
@media (max-width: 420px) {
  .earn-form { grid-template-columns: 1fr; }
  .earn-form .earn-proof { flex-direction: column; }
  .earn-form .earn-proof input[inputmode="numeric"] { flex: 1 1 auto; }
}
@media (max-width: 420px) {
  .earn-units { font-size: 26px; }
  .earn-ask-line { flex-direction: column; }
  .earn-ask-line > .go { min-height: 48px; }
  .earn-withdraw { width: 100%; justify-content: center; }
  .earn-bars { height: 64px; }
}

/* The end of the FANS list while more conversations wait to be read (2026-10-14). */
.more-fans {
  display: block; width: 100%; margin: 6px 0 12px; padding: 12px; border: 0; background: none;
  font: 500 11.5px/1.3 var(--mono); letter-spacing: 0.06em; color: var(--ink-mute); cursor: pointer;
}
.more-fans:hover { color: var(--ink); }
/* The top of a conversation while older messages are being read (2026-10-14). */
.older-note { text-align: center; padding: 8px 0 4px; font: 500 11px/1.3 var(--mono); color: var(--ink-mute); }

/* --- our own pickers (2026-10-14) -----------------------------------------------
 * The closed field: the panel's chevron instead of the browser's, and a calendar mark
 * on a date field. The open list and calendar are ours (session.js), drawn in the
 * panel's glass with its own thin scrollbar — not the browser's white box. */
select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  padding-right: 38px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23c9c3dc' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center; background-size: 14px;
}
select.picking, input.picking { box-shadow: inset 0 0 0 1px var(--accent), 0 0 0 3px oklch(80% 0.13 200 / 0.18); }
input[type="date"], input[type="datetime-local"] {
  cursor: pointer; color-scheme: dark; padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23c9c3dc' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='5' width='17' height='15.5' rx='3'/%3E%3Cpath d='M3.5 10h17M8 3v4M16 3v4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center; background-size: 16px;
}
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator { opacity: 0; width: 0; margin: 0; }

.pick-menu {
  position: fixed; z-index: 1000; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 2px; padding: 6px;
  border-radius: 14px; background: oklch(22% 0.035 285 / 0.98);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.14), 0 22px 50px -18px oklch(5% 0.05 285 / 0.95);
  backdrop-filter: blur(14px);
  animation: pick-in 140ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes pick-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.pick-option {
  flex: none; display: block; width: 100%; height: auto; padding: 10px 12px; border: 0;
  border-radius: 10px; text-align: left; cursor: pointer; background: transparent;
  box-shadow: none; font: 400 14.5px/1.3 var(--sans); color: var(--ink);
}
.pick-option:hover, .pick-option:focus-visible { background: oklch(100% 0 0 / 0.08); outline: none; }
.pick-option.chosen { background: oklch(80% 0.13 200 / 0.18); color: var(--ink-strong);
  box-shadow: inset 2px 0 0 var(--accent); }
.pick-option:disabled { opacity: 0.4; cursor: default; }
.pick-hint { margin-left: 8px; font: 400 12px/1.3 var(--mono); color: var(--ink-faint); }

.pick-calendar { flex-direction: row; align-items: stretch; gap: 8px; flex-wrap: wrap; padding: 10px; }
.pick-month { display: flex; flex-direction: column; gap: 8px; width: 252px; }
.pick-month-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.pick-month-head b { font: 600 14px/1.2 var(--sans); color: var(--ink-strong); }
.pick-nav {
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 9px; cursor: pointer;
  font: 600 18px/1 var(--sans); color: var(--ink); background: oklch(100% 0 0 / 0.06);
}
.pick-nav:hover { background: oklch(100% 0 0 / 0.12); }
.pick-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.pick-weekday { text-align: center; padding: 4px 0; font: 500 10.5px/1.2 var(--mono); color: var(--ink-mute); }
.pick-day {
  height: 34px; padding: 0; border: 0; border-radius: 9px; cursor: pointer; background: transparent;
  font: 500 13px/1 var(--sans); color: var(--ink);
}
.pick-day:hover { background: oklch(100% 0 0 / 0.1); }
.pick-day.today { box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.25); }
.pick-day.chosen { background: linear-gradient(135deg, oklch(86% 0.17 160), oklch(82% 0.13 200));
  color: oklch(20% 0.04 200); font-weight: 700; }
.pick-day:disabled { opacity: 0.25; cursor: default; background: transparent; }
.pick-clock { display: flex; gap: 4px; height: 262px; }
.pick-times { display: flex; flex-direction: column; gap: 2px; width: 58px; overflow-y: auto; padding-right: 2px; }
.pick-times .pick-option { text-align: center; padding: 8px 0; font-family: var(--mono); }
.pick-done {
  flex: 1 0 100%; height: 38px; border: 0; border-radius: 10px; cursor: pointer;
  font: 700 13.5px/1 var(--sans); color: oklch(20% 0.04 200);
  background: linear-gradient(135deg, oklch(86% 0.17 160), oklch(82% 0.13 200));
}

/* Scrollbars that belong to the panel: thin, rounded, in its own ink — everywhere, and
   in the menus above most of all. */
* { scrollbar-width: thin; scrollbar-color: oklch(100% 0 0 / 0.22) transparent; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: oklch(100% 0 0 / 0.2); border-radius: 999px;
  border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background-color: oklch(100% 0 0 / 0.34); }
@media (prefers-reduced-motion: reduce) { .pick-menu { animation: none; } }

/* WORK, an admin's section for creators with no team (2026-10-14). */
.solo-work { display: flex; flex-direction: column; gap: 10px; padding: 14px 4px 16px; }
.solo-work .detail-label.work-group { padding: 0 4px; }
.solo-list { display: flex; flex-direction: column; gap: 10px; }
.solo-row { display: flex; flex-direction: column; gap: 8px; padding: 12px; border-radius: 16px;
  background: var(--raise); box-shadow: inset 0 0 0 1px var(--line-soft); }
.solo-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.solo-count { flex: 1 1 auto; font: 400 11.5px/1.3 var(--mono); color: var(--ink-mute); }
.solo-row .cards { padding: 0; }
/* Changing a task, correcting its words (2026-10-14). */
.task-edit { display: flex; flex-direction: column; gap: 8px; }
.words-label { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.chip.edit-task { gap: 6px; }

/* Item 209c: the profile's pictures on POSTED, and the catalogue line on a connection. */
.profile-now { display: flex; flex-direction: column; gap: 6px; padding-bottom: 6px; }
.profile-tiles { grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); }
.profile-tiles .profile-tile { margin: 0; cursor: default; }
/* Item 251: a role is its tile and, under it, what can be done with it. */
.profile-tiles .profile-role { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.profile-tiles .profile-role .chips { gap: 4px; }
.profile-tiles .profile-tile .name { white-space: normal; }
.catalogue-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.portal-thumb-missing { display: grid; place-items: center; width: 100%; height: 100%;
  min-height: 44px; color: var(--ink-mute); background: var(--raise); border-radius: 8px; }

/* Item 209f-ii: the post editor on POSTED — a photo taken off, one more added. */
.notif-body .chip.remove-file { align-self: flex-start; margin-top: 6px; }
.add-photo { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.published-files { display: flex; flex-direction: column; gap: 8px; }
