/* ---------------------------------------------------------------------------
   Telegram Subscriptions — interface styles

   Light by intent, not by OS preference: the operator asked for light, so the
   palette is stated outright rather than following prefers-color-scheme.

   Colours come from a validated data-visualisation palette. The chart hue and
   the status colours were checked for contrast and colour-blind separation
   against this exact surface, so they are shared between the charts and the
   rest of the interface rather than picked twice.
--------------------------------------------------------------------------- */

:root {
  color-scheme: light;

  /* surfaces */
  --plane:        #f7f6f3;   /* page behind the cards */
  --surface:      #ffffff;   /* cards, tables, charts */
  --surface-sunk: #f9f9f7;   /* inputs, tracks, hovers */
  --rail:         #ffffff;   /* sidebar */

  /* ink */
  --ink:          #0b0b0b;
  --ink-2:        #52514e;
  --ink-muted:    #898781;

  /* lines */
  --line:         #e7e6e1;
  --line-strong:  #d7d6cf;

  /* Accent — green, and the loudest thing on the page.
     Deep enough to carry white text on a button and to read as a chart line,
     which a lime is not: #c9f24d on white is roughly 1.4:1 and unreadable. The
     lime lives on --pop instead, where it only ever sits under dark text. */
  --accent:       #0f9d63;
  --accent-sunk:  #e6f7ee;
  --accent-ink:   #07714a;
  --accent-deep:  #0b7d4e;

  /* The striking one. Dark ink only, never white, never as text. */
  --pop:          #c8f24d;
  --pop-ink:      #1b2b0a;

  /* status — amber and red keep their jobs. "Good" is the accent, because on
     this platform good news and the brand are the same green, and two greens a
     shade apart would read as a mistake rather than a distinction. */
  --good:         #0f9d63;
  --good-sunk:    #e6f7ee;
  --good-ink:     #07714a;
  --warn:         #e8912a;
  --warn-sunk:    #fdf3e4;
  --warn-ink:     #8a5209;
  --danger:       #d0453b;
  --danger-sunk:  #fbeceb;
  --danger-ink:   #a32a22;

  /* Categorical tints, for saying what a figure is ABOUT. Reserved for the
     icon chip on a card — never for a chart series, which stays one colour. */
  --t-green:      #0f9d63;  --t-green-sunk:  #e6f7ee;
  --t-blue:       #2a78d6;  --t-blue-sunk:   #eaf2fd;
  --t-violet:     #6f4fd8;  --t-violet-sunk: #efeafd;
  --t-amber:      #e8912a;  --t-amber-sunk:  #fdf3e4;
  --t-rose:       #d0453b;  --t-rose-sunk:   #fbeceb;
  --t-teal:       #0e9aa7;  --t-teal-sunk:   #e4f6f8;

  /* chart chrome */
  /* The flow canvas: its dotted ground, and the lines joining its cards. */
  --flow-dot:     #dcdbd4;
  --flow-line:    #cfcec7;

  --grid:         #ecebe5;
  --baseline:     #d7d6cf;

  /* The hover panel on a chart. Its own tokens, because it has to contrast
     with the chart rather than follow the page's text colour. */
  --tip-bg:       #0b0b0b;
  --tip-edge:     transparent;
  --tip-ink:      #ffffff;
  --tip-sub:      rgba(255,255,255,.68);
  /* Calendar. The band between two chosen days is a wash of the accent; the two
     ends are the accent solid. --cal-edge is a step deeper than --accent
     because white on --accent is 3.5:1, which is fine for a chart line and not
     fine for a number somebody has to read. */
  --cal-band:     #e6f7ee;
  --cal-edge:     #0b7d4e;
  --cal-edge-ink: #ffffff;

  /* The sign-in page's left half: a tinted ground and the two washes on it. */
  --gate-ground:  #f2f4f1;
  --gate-glow-a:  rgba(15,157,99,.30);
  --gate-glow-b:  rgba(200,242,77,.34);

  --radius:       16px;
  --radius-sm:    10px;
  --radius-pill:  999px;

  /* Type. The display face is the system serif — ui-serif is New York on
     Apple platforms, Charter or Georgia elsewhere. A web font is not an option
     here: the content security policy is default-src 'none' with no font-src,
     so nothing loads from anywhere, and that is worth more than an exact
     typeface. */
  --serif: ui-serif, "New York", "Iowan Old Style", Charter, Georgia, "Times New Roman", serif;
  --sans:  system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* Codes read character by character, so they get a face where 0 and O, and
     1 and l, are unmistakable. */
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --shadow:       0 1px 2px rgba(11,11,11,.03), 0 4px 16px rgba(11,11,11,.04);
  --shadow-lift:  0 2px 4px rgba(11,11,11,.04), 0 10px 30px rgba(11,11,11,.07);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  display: flex;
  min-height: 100vh;
  background: var(--plane);
  color: var(--ink);
  font: 14px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--accent); }

/* ----------------------------------------------------------------- sidebar */

/* The rail stands still. It is its own surface, fixed to the viewport, and the
   page scrolls past it rather than taking it along — losing the navigation
   halfway down a long table was the complaint. */
/* The rail is a panel in its own right: it floats clear of the window edge on
   all four sides rather than being welded to it, and the page scrolls past it
   rather than taking it along. */
.side {
  width: 252px;
  flex: none;
  position: sticky;
  top: 14px;
  height: calc(100vh - 28px);
  margin: 14px 0 14px 14px;
  background: var(--rail);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  padding: 18px 14px 16px;
  overflow: hidden;
}
.brand { display: flex; gap: 10px; align-items: center; padding: 2px 8px 16px; }
.brand strong {
  display: block; font-family: var(--serif); font-size: 17px;
  letter-spacing: -.012em; font-weight: 560;
}
.brand small {
  display: block; color: var(--ink-muted); font-size: 11.5px; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; max-width: 148px; white-space: nowrap;
}
.mark {
  width: 32px; height: 32px; flex: none; border-radius: 10px;
  background: linear-gradient(140deg, #0f9d63, #7fce4f);
}
/* One scrolling region, and only if the viewport is genuinely too short for
   the menu. On any normal screen nothing here moves at all. */
.side .nav { flex: 1; overflow-y: auto; scrollbar-width: none; margin: 0 -2px; padding: 0 2px; }
.side .nav::-webkit-scrollbar { width: 0; }
.side ul { list-style: none; margin: 0 0 11px; padding: 0; }
.side ul:last-child { margin-bottom: 0; }
.nav-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .085em;
  color: var(--ink-muted); font-weight: 650; padding: 0 12px 6px; margin: 0;
}
.side a {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 12px; margin-bottom: 2px; border-radius: var(--radius-sm);
  color: var(--ink-2); text-decoration: none; font-size: 15px; line-height: 1.45;
  font-weight: 480;
  transition: background .12s ease, color .12s ease;
}
.side a:hover { background: var(--surface-sunk); color: var(--ink); }
.side a.on { background: var(--accent-sunk); color: var(--accent-ink); font-weight: 600; }
/* A quiet marker on the active row, rather than a heavier fill. */
.side a.on::before {
  content: ""; width: 3px; height: 17px; border-radius: 2px;
  background: var(--accent); margin-left: -5px;
}
.side a:not(.on)::before { content: ""; width: 3px; margin-left: -5px; }
.out { margin-top: 10px; }
.out button {
  width: 100%; padding: 9px; border: 1px solid var(--line); background: none;
  color: var(--ink-2); border-radius: var(--radius-sm); cursor: pointer; font-size: 13.5px;
}
.out button:hover { background: var(--surface-sunk); color: var(--ink); }

/* -------------------------------------------------------------------- main */

/* The content column is centred in whatever space the sidebar leaves, rather
   than pinned to its left edge. Capped, because a dashboard read across 2000px
   is a dashboard nobody reads: the cap is the measure, the auto margins put
   the leftover space on both sides instead of dumping it all on the right. */
main {
  flex: 1;
  min-width: 0;
  margin-inline: auto;
  padding: 34px 44px 72px;
  max-width: 1320px;
}
main.bare { display: grid; place-items: center; max-width: none; margin: 0; }

/* --------------------------------------------------------- signed-in devices

   A row per place this account can be used from, with the device in the
   reader's own hand marked. The question being asked of this list is "is one of
   these not me", and it can only be answered once they can find themselves in
   it — so the current row is the one with colour on it. */

.device-list { list-style: none; margin: 6px 0 18px; padding: 0; }
.device {
  display: grid; grid-template-columns: 34px minmax(0, 1.6fr) minmax(0, 1fr) auto;
  gap: 14px; align-items: center;
  padding: 15px 4px; border-bottom: 1px solid var(--line);
}
.device:last-child { border-bottom: 0; }
.device-icon {
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 10px;
  background: var(--surface-sunk); color: var(--ink-2);
}
.device-icon svg { width: 17px; height: 17px; }
.device.here .device-icon { background: var(--accent-sunk); color: var(--accent-ink); }
.device-what strong, .device-where strong, .device-when strong {
  display: block; font-size: 14px; font-weight: 600; letter-spacing: -.008em;
}
.device-what .pill { margin-left: 0; vertical-align: middle; }
.device-detail { display: block; font-size: 12.5px; color: var(--ink-muted); margin-top: 2px; }
.device-where strong { font-weight: 550; }
.device-ip {
  display: block; font-size: 12px; color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.device-when { text-align: right; }
.device-when strong { font-weight: 550; }

@media (max-width: 640px) {
  .device { grid-template-columns: 34px minmax(0, 1fr); }
  .device-where, .device-when { grid-column: 2; text-align: left; }
}

/* ------------------------------------------------------------- form pages

   A page whose whole content is a form is built around it: a narrow column in
   the middle of the window, the heading centred over it, and everything inside
   set larger than it is elsewhere. A form pinned to the left of a 1300px column
   with nothing to its right reads as something that failed to load — and a form
   is the one thing on a screen that is asking to be acted on, so it gets to be
   the thing the page is arranged around. Which pages these are is `formPages`
   in server.go, not a guess made here. */

main.form-page:not(.bare) { max-width: 760px; padding-top: 44px; }
/* Except the two that carry a phone preview beside the form, which need the
   room for both. Still centred; just a wider middle. */
main.form-page:has(.compose) { max-width: 1120px; }

main.form-page > h1,
main.form-page > .lede,
main.form-page > .progress { text-align: center; }
main.form-page > h1 { font-size: 38px; margin-bottom: 10px; }
main.form-page > .lede {
  font-size: 15.5px; max-width: 52ch; margin-left: auto; margin-right: auto;
}
/* The back button keeps to the left, where a way out belongs — centring it
   would put it under the title like another choice to make. */
main.form-page > .button.back { margin-bottom: 20px; }

/* The panel itself, given more air and a heavier hand. */
main.form-page .panel {
  max-width: none; padding: 30px 34px 28px; border-radius: 16px;
}
main.form-page .panel.inset { padding: 18px 20px; border-radius: 12px; }
main.form-page .form { max-width: none; }
main.form-page .wizard h2 { font-size: 20px; margin-bottom: 6px; }
main.form-page .step-intro { font-size: 13.5px; margin-bottom: 22px; }

/* Bigger targets and darker labels: a field somebody has to read and fill in
   should not be set smaller than the prose explaining it. */
main.form-page .form label { font-size: 14px; color: var(--ink); margin-bottom: 18px; }
main.form-page .form label .hint,
main.form-page .form label .opt { color: var(--ink-muted); }
main.form-page .form input[type=text], main.form-page .form input[type=number],
main.form-page .form input[type=password], main.form-page .form input[type=email],
main.form-page .form input[type=date], main.form-page .form input[type=time],
main.form-page .form select, main.form-page .form textarea {
  padding: 12px 14px; font-size: 15px; border-radius: 10px; margin-top: 7px;
}
main.form-page .form fieldset { padding: 18px 20px; border-radius: 12px; margin-bottom: 20px; }
main.form-page .form label.choice, main.form-page label.choice {
  padding: 15px 16px; border-radius: 12px; font-size: 14.5px;
}
main.form-page label.choice .choice-text small { font-size: 12.5px; }
main.form-page .choices { gap: 8px; }

/* The two controls that end a step, sized like decisions. */
main.form-page .wizard-nav { margin-top: 28px; padding-top: 20px; }
main.form-page .wizard-nav button, main.form-page .wizard-nav .button {
  padding: 12px 24px; font-size: 14.5px; border-radius: 10px;
}
main.form-page .panel > button[type=submit],
main.form-page .panel > form > button[type=submit] {
  padding: 12px 24px; font-size: 14.5px; border-radius: 10px;
}

/* The step rail, drawn at the size of a heading rather than a footnote. */
main.form-page .progress { margin-bottom: 28px; }
main.form-page .progress .dot { width: 32px; height: 32px; font-size: 13px; }
main.form-page .progress li::before { top: 16px; }
main.form-page .progress li { font-size: 12.5px; }
main.form-page .progress .step-label { font-size: 12.5px; }

@media (max-width: 720px) {
  main.form-page:not(.bare) { padding-top: 26px; }
  main.form-page > h1 { font-size: 30px; }
  main.form-page .panel { padding: 22px 20px; }
}

/* The display face carries the headings and the figures — the things read at a
   glance. Interface chrome stays sans, where a serif would only slow it down. */
h1 {
  font-family: var(--serif);
  font-size: 31px; line-height: 1.2; margin: 0 0 6px;
  letter-spacing: -.018em; font-weight: 550;
}
h2 {
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-muted); margin: 44px 0 16px; font-weight: 650;
}
.lede { color: var(--ink-2); font-size: 14.5px; margin: 0 0 30px; max-width: 68ch; }
.empty {
  color: var(--ink-muted); background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 28px; text-align: center; font-size: 14px;
}

/* ------------------------------------------------------------------- cards */

.cards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(178px, 1fr)); gap: 16px;
}
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 20px 22px; box-shadow: var(--shadow);
  transition: box-shadow .15s ease;
}
.card:hover { box-shadow: var(--shadow-lift); }
.card .n {
  display: block; font-family: var(--serif); font-size: 30px; font-weight: 550;
  letter-spacing: -.02em; line-height: 1.15;
}
/* Money figures carry a currency code, so they run long. Slightly smaller and
   kept on one line reads better than a number broken across two. */
.card .n.money { font-size: 25px; white-space: nowrap; }
.card .l { display: block; font-size: 12.5px; color: var(--ink-muted); margin-top: 4px; }
.card .sub { display: block; font-size: 12px; color: var(--ink-2); margin-top: 6px; }
.card.warn .n { color: var(--warn-ink); }
.card.good .n { color: var(--good-ink); }

/* ------------------------------------------------------------------ charts */

.chart-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
.chart-grid.one { grid-template-columns: 1fr; }
/* A grid's gap stops at its own edge, so two grids stacked one on the other
   touch. Prefer one grid; this is the safety net for where there are two. */
.chart-grid + .chart-grid { margin-top: 18px; }
/* An odd panel left alone on the last row takes the full width rather than
   half of it, so the row does not read as a column that failed to load. */
.chart-grid > .panel-chart:last-child:nth-child(odd) { grid-column: 1 / -1; }
.panel-chart {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 20px 22px 16px; box-shadow: var(--shadow);
}
.panel-chart header { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.panel-chart h3 {
  margin: 0; font-family: var(--serif); font-size: 16px; font-weight: 550;
  letter-spacing: -.012em;
}
.panel-chart .total { font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.panel-chart .caption { margin: 2px 0 10px; font-size: 12px; color: var(--ink-muted); }
.chart { width: 100%; height: auto; display: block; margin-top: 4px; }
.chart-empty {
  color: var(--ink-muted); font-size: 13px; text-align: center; padding: 40px 0 44px; margin: 0;
}

/* Marks stay thin; the grid is a hairline one shade off the surface. */
.grid { stroke: var(--grid); stroke-width: 1; }
.axis-y { fill: var(--ink-muted); font-size: 10px; text-anchor: end; font-variant-numeric: tabular-nums; }
.axis-x { fill: var(--ink-muted); font-size: 10px; text-anchor: middle; }
/* --- line series ---------------------------------------------------------

   The whole hover interaction is CSS. Each day owns a transparent full-height
   target; hovering it reveals that day's marker, guide and panel. No script
   runs on these pages, and a tooltip is not worth changing that. */

.line-mark {
  fill: none; stroke: var(--accent); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.line-area { fill: var(--accent-sunk); stroke: none; }

.pt .hit { fill: transparent; }
.pt .dot {
  fill: var(--surface); stroke: var(--accent); stroke-width: 2;
  opacity: 0; vector-effect: non-scaling-stroke;
}
.pt .guide { stroke: var(--accent); stroke-width: 1; opacity: 0; vector-effect: non-scaling-stroke; }
.pt .tip { opacity: 0; }
.pt:hover .dot, .pt:hover .guide, .pt:hover .tip { opacity: 1; }
.pt:hover .guide { opacity: .28; }

/* Today is still in progress, so its marker is recessive until asked about. */
.pt-muted .dot { stroke: #9fdcbe; }
.pt-muted:hover .dot { stroke: var(--accent); }

/* The hover panel has its own three tokens rather than borrowing --ink.
   Borrowing was the bug: --ink is near-black on a light page and near-white on
   a dark one, so in dark mode the panel filled white and the white text on it
   disappeared. A tooltip has to contrast with the CHART, not follow the text
   colour, so it states what it is in both schemes. */
.tip-box { fill: var(--tip-bg); stroke: var(--tip-edge); stroke-width: 1; }
.tip-head { fill: var(--tip-sub); font-size: 10.5px; }
.tip-value { fill: var(--tip-ink); font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }

.hbar-label { fill: var(--ink-2); font-size: 12px; text-anchor: end; }
.hbar-track { fill: var(--surface-sunk); }
.hbar-fill { fill: var(--accent); }
.hbar:hover .hbar-fill { fill: var(--accent-ink); }
.hbar-value {
  fill: var(--ink); font-size: 12px; text-anchor: end; font-variant-numeric: tabular-nums;
}
.spark { width: 100%; height: 28px; display: block; margin-top: 8px; }
.spark-line { fill: none; stroke: var(--accent); stroke-width: 2; vector-effect: non-scaling-stroke; }
.spark-area { fill: var(--accent-sunk); stroke: none; }

/* ------------------------------------------------------------------ tables */

table {
  width: 100%; border-collapse: collapse; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  box-shadow: var(--shadow);
}
th {
  text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-muted); padding: 11px 16px; border-bottom: 1px solid var(--line);
  font-weight: 620; background: var(--surface-sunk);
}
td { padding: 12px 16px; border-bottom: 1px solid var(--line); font-size: 13.5px; }
tr:last-child td { border-bottom: none; }
tbody tr.link:hover { background: var(--surface-sunk); }
tbody tr.link td:first-child { font-weight: 550; }
td a { text-decoration: none; }
td a:hover { text-decoration: underline; }
.r { text-align: right; }
/* Figures and dates are read as units; wrapping them mid-value hurts scanning. */
.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.why { color: var(--ink-muted); font-size: 12.5px; }

.pill {
  display: inline-block; padding: 2px 9px; border-radius: 20px; font-size: 11.5px;
  background: var(--surface-sunk); color: var(--ink-2); border: 1px solid var(--line);
  white-space: nowrap;
}
.pill.ok     { background: var(--good-sunk);   color: var(--good-ink);   border-color: transparent; }
.pill.warn   { background: var(--warn-sunk);   color: var(--warn-ink);   border-color: transparent; }
.pill.danger { background: var(--danger-sunk); color: var(--danger-ink); border-color: transparent; }

/* ---------------------------------------------------------------- messages */

.notice, main .error, .field-error {
  border-radius: var(--radius-sm); font-size: 13px; margin: 0 0 18px; padding: 10px 13px;
}
.notice { background: var(--good-sunk); color: var(--good-ink); }
main .error { background: var(--danger-sunk); color: var(--danger-ink); }
.field-error { display: block; margin: 5px 0 0; padding: 0; color: var(--danger-ink); font-size: 12px; }
.hint { display: block; margin-top: 5px; font-size: 12px; color: var(--ink-muted); }
.alt { margin: 22px 0 0; font-size: 13px; color: var(--ink-muted); }

/* ------------------------------------------------------------------- forms */

.panel {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 22px 24px; max-width: 720px; box-shadow: var(--shadow); margin-bottom: 16px;
}
.panel h2 {
  margin: 0 0 10px; font-size: 14px; text-transform: none; letter-spacing: -.01em;
  color: var(--ink); font-weight: 600;
}
.panel code, .steps code {
  font-size: 12px; background: var(--surface-sunk); padding: 3px 7px; border-radius: 6px;
  border: 1px solid var(--line); word-break: break-all;
}
ol.plain { margin: 0 0 12px; padding-left: 20px; font-size: 13.5px; }
ol.plain li { margin-bottom: 5px; }

.form { max-width: 660px; }
.form label { display: block; font-size: 13px; color: var(--ink-2); margin-bottom: 15px; }
.form input[type=text], .form input[type=number], .form input[type=password],
.form input[type=email], .form select, form.stack input {
  display: block; width: 100%; margin-top: 5px; padding: 9px 12px; font-size: 14px;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink);
}
.form input:focus, .form select:focus, form.stack input:focus, .login input:focus {
  outline: 2px solid var(--accent); outline-offset: -1px; border-color: transparent;
}
.form .row { display: flex; gap: 12px; flex-wrap: wrap; }
.form .row label { flex: 1; min-width: 120px; }
.form fieldset {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 14px 16px; margin: 0 0 16px;
}
.form legend {
  font-size: 11px; color: var(--ink-muted); text-transform: uppercase;
  letter-spacing: .06em; padding: 0 6px; font-weight: 620;
}
.form label.check {
  display: flex; align-items: center; gap: 8px; color: var(--ink);
  font-size: 13.5px; margin-bottom: 8px;
}
.form label.check input { width: auto; margin: 0; }
.opt { color: var(--ink-muted); font-weight: 400; font-size: 11px; }

button, .button, .form button, form.stack button, form.confirm button {
  padding: 9px 17px; border: none; border-radius: var(--radius-sm);
  background: var(--accent); color: #fff; font-size: 13.5px; font-weight: 550;
  cursor: pointer; font-family: inherit;
}
button:hover, .button:hover { filter: brightness(1.06); }
.button { display: inline-block; text-decoration: none; margin-bottom: 18px; }
button.danger, form.confirm button.danger {
  background: var(--surface); color: var(--ink-2); border: 1px solid var(--line-strong);
}
button.danger:hover { border-color: var(--danger); color: var(--danger-ink); background: var(--danger-sunk); }

form.stack { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
form.stack input { flex: 1; min-width: 220px; margin-top: 0; }
form.stack input[type=file] { padding: 8px; border-style: dashed; }
form.inline { display: inline; margin: 0; }
form.confirm {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px; margin: 16px 0; box-shadow: var(--shadow);
}
form.confirm button { margin-right: 12px; }
form.confirm .hint { display: inline; margin: 0; }

/* ------------------------------------------------------------------- login */

/* The sign-in card, set at the same weight as the form pages inside: it is the
   first thing anybody sees, and a small tight box is not a welcome. */
.login {
  width: 420px; max-width: 100%; background: var(--surface);
  border: 1px solid var(--line); border-radius: 16px; padding: 38px 36px;
  box-shadow: var(--shadow-lift);
}
.login h1 { font-size: 25px; margin: 0 0 6px; }
.login .sub { color: var(--ink-muted); font-size: 14px; margin: 0 0 26px; }
.login label { display: block; font-size: 14px; color: var(--ink); margin-bottom: 18px; }
.login input {
  display: block; width: 100%; margin-top: 7px; padding: 12px 14px; font-size: 15px;
  border: 1px solid var(--line-strong); border-radius: 10px;
  background: var(--surface); color: var(--ink);
}
.login button {
  width: 100%; padding: 13px; margin-top: 8px; font-size: 15px; border-radius: 10px;
}
.login .alt { text-align: center; }

/* ------------------------------------------------------------------- setup */

.steps { list-style: none; counter-reset: step; margin: 0; padding: 0; }
.steps li {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px 20px; margin-bottom: 12px; box-shadow: var(--shadow);
}
.steps li.done { border-left: 3px solid var(--good); }
.steps li.warn { border-left: 3px solid var(--warn); }
.steps h2 {
  margin: 0 0 8px; font-size: 14.5px; text-transform: none; letter-spacing: -.01em;
  color: var(--ink); font-weight: 600;
}
.steps p { margin: 6px 0; font-size: 13.5px; }

/* ----------------------------------------------------------------- imports */

.filters { display: flex; gap: 6px; margin: 0 0 12px; flex-wrap: wrap; }
.filters a {
  padding: 5px 11px; border-radius: 20px; font-size: 12px; text-decoration: none;
  color: var(--ink-2); border: 1px solid var(--line); background: var(--surface);
}
.filters a:hover { background: var(--surface-sunk); }
.filters a.on { background: var(--accent); color: #fff; border-color: transparent; }

/* ------------------------------------------------------------ profile page */

.profile-head {
  display: flex; align-items: flex-start; gap: 16px; margin-bottom: 6px;
}
.avatar {
  width: 52px; height: 52px; flex: none; border-radius: 50%;
  background: var(--accent-sunk); color: var(--accent-ink);
  display: grid; place-items: center; font-size: 20px; font-weight: 600;
}
.profile-head h1 { margin: 0; }
.meta { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 6px 0 0; }
.meta span { font-size: 12.5px; color: var(--ink-muted); }
.meta strong { color: var(--ink-2); font-weight: 550; }
.two-col { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
/* Back is a control, not a sentence with an arrow in front of it. It is the
   size of a control and is hit like one on a phone. */
.button.back {
  display: inline-flex; align-items: center; gap: 6px;
  margin-bottom: 16px; padding: 7px 13px; font-size: 13px; font-weight: 520;
}
.button.back svg { margin-left: -2px; }
.wizard-nav .button.back { margin-bottom: 0; }

/* --------------------------------------------------------------- responsive */

@media (max-width: 980px) {
  .chart-grid, .two-col { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
  body { flex-direction: column; }
  .side {
    width: auto; flex-direction: row; align-items: center; padding: 10px;
    gap: 10px; border-right: none; border-bottom: 1px solid var(--line);
  }
  .side ul { display: flex; gap: 4px; overflow-x: auto; }
  .brand small, .out { display: none; }
  main { padding: 20px 16px 40px; }
  table { display: block; overflow-x: auto; }
}

/* ------------------------------------------------- bot conversation preview

   A deliberately Telegram-shaped mock: the tenant is writing a message that
   will be read inside Telegram, so previewing it as a plain form field would
   hide exactly the thing they are editing — how it lands.                    */

.tg-preview {
  background: #17212b; border-radius: var(--radius); padding: 14px;
  border: 1px solid #0f1720;
}
.tg-head { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; }
.tg-avatar {
  width: 30px; height: 30px; border-radius: 50%; background: #d17ac4; color: #fff;
  display: grid; place-items: center; font-size: 13px; font-weight: 600;
  text-transform: uppercase;
}
.tg-head strong { display: block; color: #fff; font-size: 13px; }
.tg-head small { display: block; color: #7c93a8; font-size: 11px; }
.tg-bubble {
  background: #1f2c39; color: #e8edf2; border-radius: 12px; padding: 12px 14px;
  font-size: 13.5px; line-height: 1.5; word-break: break-word;
}
.tg-bubble b, .tg-bubble strong { color: #fff; }
.tg-bubble a { color: #6ab3f3; }
.tg-prompt { margin-top: 12px; }
.tg-button {
  background: #1f2c39; color: #6ab3f3; border-radius: 10px; padding: 11px 12px;
  margin-top: 6px; font-size: 13.5px; text-align: center; word-break: break-word;
}
.tg-button.empty { color: #7c93a8; font-size: 12.5px; }

/* textareas share the input treatment */
.form textarea {
  display: block; width: 100%; margin-top: 5px; padding: 10px 12px; font-size: 13.5px;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink); font-family: inherit;
  line-height: 1.5; resize: vertical;
}
.form textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: transparent; }

/* --------------------------------------------------------------- filters

   Built from anchors and <details> rather than <select> elements, so the
   browser never draws widget chrome of its own. A <details> is a native
   disclosure control — it opens and closes without script — and unlike a
   <select> its panel is ours to style.                                      */

.filters-panel {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 14px; margin: 0 0 16px;
  box-shadow: var(--shadow);
}

/* search ------------------------------------------------------------------ */

.filters-panel .search { display: flex; align-items: center; gap: 8px; margin: 0; }
.filters-panel .search-icon {
  position: absolute; margin-left: 11px; color: var(--ink-muted);
  display: flex; pointer-events: none;
}
.filters-panel .search input[type=text] {
  flex: 1; min-width: 0; margin: 0;
  padding: 9px 12px 9px 34px; font-size: 13.5px; font-family: inherit;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--surface-sunk); color: var(--ink);
  -webkit-appearance: none; appearance: none;
}
.filters-panel .search input::placeholder { color: var(--ink-muted); }
.filters-panel .search input:focus {
  outline: 2px solid var(--accent); outline-offset: -1px;
  border-color: transparent; background: var(--surface);
}
.filters-panel .search button { padding: 9px 16px; flex: none; }

.filter-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px;
}

/* segmented control ------------------------------------------------------- */

.segmented {
  display: inline-flex; background: var(--surface-sunk); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 2px; gap: 2px; flex-wrap: wrap;
}
.segmented a {
  padding: 5px 11px; border-radius: 6px; font-size: 12.5px; text-decoration: none;
  color: var(--ink-2); white-space: nowrap;
}
.segmented a:hover { background: var(--surface); color: var(--ink); }
.segmented a.on {
  background: var(--surface); color: var(--ink); font-weight: 550;
  box-shadow: 0 1px 2px rgba(11,11,11,.06);
}

/* dropdown ---------------------------------------------------------------- */

.dropdown { position: relative; }
.dropdown > summary {
  display: inline-flex; align-items: baseline; gap: 6px; cursor: pointer;
  padding: 8px 11px; border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--surface); font-size: 13px; color: var(--ink);
  list-style: none; white-space: nowrap; user-select: none;
}
/* The browser's own disclosure triangle is chrome we are replacing. */
.dropdown > summary::-webkit-details-marker { display: none; }
.dropdown > summary::marker { content: ""; }
.dropdown > summary:hover { background: var(--surface-sunk); }
.dropdown[open] > summary {
  border-color: var(--accent); outline: 2px solid var(--accent-sunk); outline-offset: -1px;
}
.dd-label { color: var(--ink-muted); font-size: 12px; }
.dd-value { font-weight: 550; max-width: 190px; overflow: hidden; text-overflow: ellipsis; }
.dd-chevron { display: flex; color: var(--ink-muted); align-self: center; }
.dropdown[open] .dd-chevron { transform: rotate(180deg); }

.dd-panel {
  position: absolute; z-index: 20; top: calc(100% + 5px); left: 0; min-width: 220px;
  max-height: 300px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 5px;
  box-shadow: 0 6px 20px rgba(11,11,11,.10), 0 1px 3px rgba(11,11,11,.06);
}
.dd-panel.narrow { min-width: 130px; }
.dd-panel a {
  display: block; padding: 7px 10px; border-radius: 6px; font-size: 13px;
  color: var(--ink-2); text-decoration: none; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.dd-panel a:hover { background: var(--surface-sunk); color: var(--ink); }
.dd-panel a.on { background: var(--accent-sunk); color: var(--accent-ink); font-weight: 550; }

.filter-row .clear {
  font-size: 12.5px; color: var(--ink-muted); text-decoration: none; padding: 0 4px;
  margin-left: auto;
}
.filter-row .clear:hover { color: var(--danger-ink); text-decoration: underline; }

.result-count { font-size: 12.5px; color: var(--ink-muted); margin: 0 0 10px; }

/* --------------------------------------------------------- form dropdowns

   Filters use <details> panels, but a form field has to submit a value, so
   these stay <select> elements. The browser's own chrome is stripped and a
   chevron drawn in its place, so the closed control — which is what is on
   screen almost all of the time — matches everything around it. The open list
   is the platform's own picker, which is the right behaviour on a phone.     */

.form select, .filters-panel select {
  -webkit-appearance: none; appearance: none;
  padding-right: 32px;
  background-image: url("data:image/svg+xml;charset=utf-8,\
%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E\
%3Cpath d='M2.5 4.5 L6 8 L9.5 4.5' fill='none' stroke='%23898781' \
stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 11px;
  cursor: pointer;
}
.form select:hover { background-color: var(--surface-sunk); }

/* Native decorations on text inputs are chrome too. */
.form input[type=text], .form input[type=number], .form input[type=password],
.form input[type=email], .form textarea {
  -webkit-appearance: none; appearance: none;
}
input[type=search]::-webkit-search-cancel-button,
input[type=search]::-webkit-search-decoration { -webkit-appearance: none; }

/* The file picker's native button, restyled to match. */
.form input[type=file], form.stack input[type=file] { cursor: pointer; }
form.stack input[type=file]::-webkit-file-upload-button,
form.stack input[type=file]::file-selector-button {
  -webkit-appearance: none; appearance: none;
  margin-right: 10px; padding: 6px 12px; cursor: pointer;
  border: 1px solid var(--line-strong); border-radius: 6px;
  background: var(--surface); color: var(--ink-2);
  font-family: inherit; font-size: 13px;
}
form.stack input[type=file]::file-selector-button:hover {
  background: var(--surface-sunk); color: var(--ink);
}

/* ---------------------------------------------------------- progress steps

   Creating a plan is four decisions, so the form shows four steps and where
   the person is among them. A progress bar is not decoration here: it is the
   answer to "how much more of this is there".                                */

.progress {
  display: flex; list-style: none; margin: 0 0 20px; padding: 0; gap: 0;
  counter-reset: step;
}
.progress li {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px;
  position: relative; font-size: 12px; color: var(--ink-muted);
}
/* The connecting rule sits behind the dots and stops at the last one. */
.progress li::before {
  content: ""; position: absolute; top: 13px; left: 50%; width: 100%; height: 2px;
  background: var(--line); z-index: 0;
}
.progress li:last-child::before { display: none; }
.progress li.done::before { background: var(--accent); }
.progress .dot {
  position: relative; z-index: 1;
  width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface); border: 2px solid var(--line);
  font-size: 12px; font-weight: 600; color: var(--ink-muted);
}
.progress li.now .dot { border-color: var(--accent); color: var(--accent-ink); }
.progress li.done .dot { background: var(--accent); border-color: var(--accent); color: #fff; }
.progress li.now .step-label { color: var(--ink); font-weight: 550; }
.progress li.done .step-label { color: var(--ink-2); }

.wizard h2 {
  margin: 0 0 4px; font-size: 16px; text-transform: none; letter-spacing: -.01em;
  color: var(--ink); font-weight: 600;
}
.step-intro { margin: 0 0 18px; }
.wizard-nav {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line);
}
.wizard-nav .back { margin: 0; }

/* -------------------------------------------------------- checks & radios

   The browser's own checkbox is drawn by the operating system and ignores
   everything around it. These are built from the same tokens as the rest.    */

.choices { display: flex; flex-direction: column; gap: 6px; }
/* Qualified with the element so it outranks `.form label { display: block }`;
   an unqualified `.choice` loses on specificity and the box collapses. */
.form label.choice, label.choice {
  display: flex; align-items: flex-start; gap: 10px; margin: 0;
  padding: 11px 13px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); cursor: pointer; font-size: 13.5px; color: var(--ink);
}
.form label.choice:hover, label.choice:hover { background: var(--surface-sunk); border-color: var(--line-strong); }
/* The real input stays in the DOM for keyboard and forms, but is not drawn. */
.choice input { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
.choice .box {
  flex: none; width: 18px; height: 18px; margin-top: 1px;
  border: 1.5px solid var(--line-strong); border-radius: 5px;
  background: var(--surface); position: relative; transition: none;
}
.choice .box.round { border-radius: 50%; }
.choice input:checked + .box { background: var(--accent); border-color: var(--accent); }
.choice input:checked + .box::after {
  content: ""; position: absolute; left: 5px; top: 1.5px;
  width: 5px; height: 10px; border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.choice input:checked + .box.round::after {
  left: 4.5px; top: 4.5px; width: 7px; height: 7px; border: none;
  border-radius: 50%; background: #fff; transform: none;
}
.choice input:focus-visible + .box { outline: 2px solid var(--accent); outline-offset: 2px; }
.form label.choice:has(input:checked), label.choice:has(input:checked) { border-color: var(--accent); background: var(--accent-sunk); }
.choice-text { display: flex; flex-direction: column; gap: 2px; }
.choice-text small { color: var(--ink-muted); font-size: 12px; }

/* ------------------------------------------------------------------ review */

.review { border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
.review-row {
  display: flex; gap: 16px; padding: 11px 14px; border-bottom: 1px solid var(--line);
  font-size: 13.5px;
}
.review-row:last-child { border-bottom: none; }
.review-row span { flex: 0 0 110px; color: var(--ink-muted); font-size: 12.5px; }
.review-row strong { font-weight: 550; }
.button-sample {
  background: #1f2c39; color: #6ab3f3; border-radius: 8px; padding: 8px 12px;
  font-weight: 500; display: inline-block;
}

tbody tr.dim td { opacity: .6; }

/* ------------------------------------------------------ system message cards */

.msg-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
.msg-card {
  display: flex; flex-direction: column; gap: 6px; text-decoration: none;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px; box-shadow: var(--shadow); color: var(--ink);
}
.msg-card:hover { border-color: var(--accent); box-shadow: 0 2px 10px rgba(42,120,214,.10); }
.msg-card-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.msg-card-head strong { font-size: 14px; font-weight: 600; letter-spacing: -.01em; }
.msg-card .chev { display: flex; color: var(--ink-muted); }
.msg-card:hover .chev { color: var(--accent); }
.msg-when { font-size: 12px; color: var(--ink-muted); }
.msg-excerpt {
  font-size: 13px; color: var(--ink-2); margin-top: 4px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* editor beside its preview */
.editor-split { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,360px); gap: 16px; align-items: start; }
.editor-split .form { max-width: none; margin: 0; }
.editor-actions { display: flex; gap: 10px; align-items: center; }
.tg-preview.sticky { position: sticky; top: 20px; }

@media (max-width: 900px) {
  .editor-split { grid-template-columns: 1fr; }
  .tg-preview.sticky { position: static; }
}

/* --------------------------------------------------------- formatting help */

.fmt-help {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-sunk); padding: 12px 14px; margin: 0 0 16px;
}
.fmt-title {
  display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-muted); font-weight: 620; margin-bottom: 8px;
}
.fmt-grid {
  display: grid; grid-template-columns: auto 1fr; gap: 5px 12px; align-items: baseline;
}
.fmt-grid code {
  font-size: 12px; background: var(--surface); border: 1px solid var(--line);
  border-radius: 5px; padding: 2px 7px; white-space: nowrap;
}
.fmt-grid span { font-size: 12.5px; color: var(--ink-2); }
.fmt-note { display: block; margin-top: 10px; font-size: 12px; color: var(--ink-muted); }
.fmt-note code { font-size: 11.5px; }

/* -------------------------------------------------------------- security */

.panel.inset, details.inset {
  background: var(--surface-sunk); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 12px 14px; margin: 12px 0 0;
}
details.inset > summary, .panel.inset > summary {
  cursor: pointer; font-weight: 560; font-size: 13.5px; color: var(--ink-2);
}
details.inset[open] > summary { margin-bottom: 10px; }

/* Enrolment: numbered steps beside the QR code. */
.enrol-step { display: flex; gap: 12px; align-items: flex-start; margin: 18px 0 10px; }
.enrol-step:first-child { margin-top: 0; }
.enrol-step .step-n {
  flex: 0 0 24px; height: 24px; border-radius: 50%;
  background: var(--accent); color: #fff;
  font-size: 12px; font-weight: 640; line-height: 24px; text-align: center;
}
.enrol-step strong { display: block; font-size: 14px; }
.enrol-step .hint { margin: 3px 0 0; }

.qr-holder {
  display: flex; justify-content: center;
  padding: 20px; margin: 4px 0 8px;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm);
}
/* A QR code is only scannable if its modules stay square and crisp. */
.qr { width: 210px; height: 210px; image-rendering: pixelated; display: block; }

code.key {
  display: inline-block; font-family: var(--mono); font-size: 14px; letter-spacing: .12em;
  padding: 8px 12px; background: var(--surface-sunk);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  word-break: break-all;
}

.code-input {
  font-size: 22px; letter-spacing: .34em; text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Recovery codes are transcribed by hand, so they get a monospace face and
   plenty of room between them. */
.codes {
  list-style: none; margin: 14px 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px;
}
.codes li {
  font-family: var(--mono); font-size: 14px; letter-spacing: .06em;
  padding: 9px 12px; background: var(--surface-sunk);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  user-select: all;
}

/* ------------------------------------------------------- formatting toolbar

   A label and its control are siblings in these fields rather than nested,
   because the toolbar sits between them and a <label> may not contain buttons:
   it would swallow the click meant for the button. */

.form .field { margin-bottom: 15px; }
.form .field > label { display: block; font-size: 13px; color: var(--ink-2); margin-bottom: 0; }

.md-tools { position: relative; margin-top: 6px; }
.md-tools + textarea { margin-top: 6px; }

.md-bar { display: flex; flex-wrap: wrap; gap: 4px; }

.form .md-btn {
  min-width: 30px; padding: 5px 9px;
  background: var(--surface-sunk); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: 6px;
  font-size: 12.5px; font-weight: 550; line-height: 1.5;
}
.form .md-btn:hover { background: var(--surface); color: var(--ink); border-color: var(--line-strong); }
.form .md-btn:active { background: var(--accent-sunk); border-color: var(--accent); color: var(--accent-ink); }
.form .md-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Each button shows what it does. */
.md-bold      { font-weight: 800 !important; }
.md-italic    { font-style: italic; font-family: Georgia, serif; }
.md-underline { text-decoration: underline; text-underline-offset: 2px; }
.md-strike    { text-decoration: line-through; }
.md-code      { font-family: var(--mono); font-size: 11px !important; letter-spacing: -.02em; }

/* The box shown when a button is pressed with nothing selected. It floats over
   the top of the field rather than pushing it down, so the text being edited
   does not jump under the cursor. */
.md-warn {
  position: absolute; z-index: 5; left: 0; top: calc(100% + 7px);
  margin: 0; padding: 7px 11px; max-width: 320px;
  background: var(--ink); color: #fff;
  border-radius: 7px; font-size: 12.5px; line-height: 1.4;
  box-shadow: 0 4px 14px rgb(0 0 0 / .17);
  opacity: 0; visibility: hidden; transform: translateY(-3px);
  transition: opacity .13s ease, transform .13s ease, visibility .13s;
  pointer-events: none;
}
.md-warn::before {
  content: ""; position: absolute; left: 13px; top: -4px;
  width: 8px; height: 8px; background: var(--ink); transform: rotate(45deg);
}
.md-warn.on { opacity: 1; visibility: visible; transform: none; }

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

/* ---------------------------------------------------- small layout helpers

   These were once style="" attributes on the elements themselves. The content
   security policy is style-src 'self', which blocks inline styles — so every
   one of them was being silently discarded and the pages had quietly stopped
   matching their own markup. Rules belong in here, where the policy allows
   them to apply. */

.notice.warn { background: var(--warn-sunk); color: var(--warn-ink); }
.form .field-emoji { flex: 0 0 110px; }
.form .field-narrow { max-width: 110px; }
.form .field-tiny { max-width: 90px; }
.form label.choice.wide { max-width: 100%; }
.row.spaced { margin-top: 16px; }
.hint.under-heading { margin: -6px 0 12px; }

/* Trigger groups: what the operator is trying to do, above the options that
   do it. */
.choice-group {
  margin: 16px 0 8px; font-size: 11px; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-muted); font-weight: 620;
}
fieldset > .choice-group:first-of-type { margin-top: 4px; }

/* The earlier period, drawn behind: dashed and recessive, so it reads as
   context rather than as a second thing to study. */
.line-prev {
  fill: none; stroke: var(--ink-muted); stroke-width: 1.5;
  stroke-dasharray: 3 3; opacity: .5;
  vector-effect: non-scaling-stroke;
}
.pt .dot-prev { fill: var(--surface); stroke: var(--ink-muted); stroke-width: 1.5; opacity: 0; }
.pt:hover .dot-prev { opacity: .75; }
.tip-was { fill: var(--tip-sub); font-size: 10.5px; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------- revenue page

   Cards carry a movement chip when the comparison is on. Colour follows
   meaning, not direction: revenue climbing is good and refunds climbing is not,
   so the card says which way is which rather than painting every rise green. */

 /* The movement sits on the label line rather than over the figure: a long
    amount like "6257.00 USD" needs the whole width, and a chip floated into
    the corner was wrapping it onto two lines. */
.cards.wide { grid-template-columns: repeat(auto-fit, minmax(212px, 1fr)); }
.cards.wide .card .l { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.cards.wide .card .delta {
  flex: none; font-style: normal;
  padding: 2px 7px; border-radius: 999px;
  font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums;
  background: var(--surface-sunk); color: var(--ink-muted);
}
.cards.wide .card .delta.up, .cards.wide .card .delta.good-down { background: var(--good-sunk); color: var(--good-ink); }
.cards.wide .card .delta.down, .cards.wide .card .delta.bad-up { background: var(--warn-sunk); color: var(--warn-ink); }

/* The comparison toggle reads as a checkbox rather than another filter pill. */
.toggle {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 12px 7px 9px; border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm); background: var(--surface);
  color: var(--ink-2); text-decoration: none; font-size: 13px;
}
.toggle:hover { background: var(--surface-sunk); color: var(--ink); }
.toggle .toggle-box {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex: none;
  border: 1px solid var(--line-strong); border-radius: 4px;
  background: var(--surface); color: transparent;
}
.toggle.on { border-color: var(--accent); background: var(--accent-sunk); color: var(--accent-ink); }
.toggle.on .toggle-box { background: var(--accent); border-color: var(--accent); color: #fff; }

.legend-prev { color: var(--ink-muted); font-size: 12px; }
.filters-panel .hint { margin: 10px 0 0; }

/* A nine-column table does not fit a narrow window. It scrolls inside its own
   box rather than pushing the whole page sideways. */
.table-scroll { overflow-x: auto; border-radius: var(--radius); }
.table-scroll table { min-width: 820px; }

/* A quieter button for an action that opens a confirmation rather than doing
   something: the destructive colour belongs on the step that spends the money,
   not on the link that asks about it. */
.button.ghost {
  background: var(--surface); border: 1px solid var(--line-strong); color: var(--ink-2);
}
.button.ghost:hover { background: var(--surface-sunk); color: var(--ink); }
.button.ghost.danger-text { color: var(--danger-ink); border-color: var(--line-strong); }
.button.ghost.danger-text:hover { background: var(--danger-sunk); border-color: var(--danger); }

/* Facts above a confirmation: what is about to happen, in plain rows. */
.facts { margin: 0 0 18px; display: grid; gap: 8px; }
.facts > div { display: flex; justify-content: space-between; gap: 16px; font-size: 13.5px; }
.facts dt { color: var(--ink-muted); margin: 0; }
.facts dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
.editor-actions .button.ghost { text-decoration: none; }

/* ------------------------------------------------------- plans and the shop

   The trial bar sits above the page rather than inside it: it is about the
   account, not about whatever is on screen. */
.plan-bar {
  margin: -14px 0 24px; padding: 10px 14px; border-radius: var(--radius-sm);
  background: var(--accent-sunk); color: var(--accent-ink); font-size: 13px;
}
.plan-bar a { color: inherit; font-weight: 600; }
.plan-bar.over { background: var(--warn-sunk); color: var(--warn-ink); }

.staff-nav { list-style: none; margin: 10px 0 0; padding: 12px 0 0; border-top: 1px solid var(--line); }
.staff-label {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-muted); font-weight: 620; padding: 0 10px 6px;
}

.tiers { display: grid; grid-template-columns: repeat(auto-fit, minmax(236px, 1fr)); gap: 18px; }
.tier {
  position: relative; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 22px 22px 24px; box-shadow: var(--shadow);
  display: flex; flex-direction: column;
}
.tier.featured, .tier.current { border-color: var(--accent); }
.tier-flag {
  position: absolute; top: -9px; left: 20px; padding: 2px 9px; border-radius: 999px;
  background: var(--accent); color: #fff; font-size: 10.5px; font-weight: 620;
  letter-spacing: .02em;
}
.tier h2, .tier h3 {
  margin: 0; font-size: 15px; font-weight: 640; letter-spacing: -.01em;
  text-transform: none; color: var(--ink);
}
.tier-price { margin: 10px 0 2px; }
.tier-price strong { font-size: 26px; font-weight: 620; letter-spacing: -.025em; }
.tier-price span { color: var(--ink-muted); font-size: 12.5px; }
.tier-blurb { margin: 6px 0 14px; color: var(--ink-2); font-size: 13px; }
.tier-features { list-style: none; margin: 0 0 18px; padding: 0; flex: 1; }
.tier-features li {
  position: relative; padding: 4px 0 4px 20px; font-size: 13px; color: var(--ink-2);
}
/* A tick drawn in CSS: no icon font, no image request. */
.tier-features li::before {
  content: ""; position: absolute; left: 3px; top: 10px;
  width: 4px; height: 8px; border: solid var(--good); border-width: 0 1.7px 1.7px 0;
  transform: rotate(45deg);
}
.tier .button, .tier button { align-self: flex-start; text-decoration: none; }

/* The public pricing page has no sidebar, so it gets its own measure. */
.shopfront { width: 100%; max-width: 1000px; margin: 0 auto; padding: 8px 20px 40px; }
.shopfront h1 { font-size: 28px; margin-bottom: 8px; }
.shopfront .lede { margin-bottom: 32px; max-width: 640px; }
.shopfront .alt { margin-top: 28px; color: var(--ink-muted); font-size: 13px; }

.usage-head {
  margin: 18px 0 10px; font-size: 12px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-muted); font-weight: 620;
}
.facts dd.at-limit { color: var(--warn-ink); font-weight: 600; }
.muted { color: var(--ink-muted); font-size: 11.5px; }

/* ------------------------------------------------------------- admin shell

   A second application, not a section of the first. Running the platform and
   running a Telegram business are different jobs, and the sidebar should say
   which one you are in before you read a word of it. */

body.admin .side { background: #14181a; border-color: #232a26; }
body.admin .side .brand strong { color: #f2f3f5; }
body.admin .side .brand small { color: #8b909a; }
body.admin .side a { color: #b9bec7; }
body.admin .side a:hover { background: #1d2027; color: #fff; }
body.admin .side a.on { background: #1e3a2b; color: #7fe0ab; }
body.admin .out button { border-color: #2b2f37; color: #b9bec7; }
body.admin .out button:hover { background: #1d2027; color: #fff; }
.admin-mark { background: linear-gradient(140deg, #1f3d2c, #4f7d5f); }

/* The one door between the two, in either direction. */
.cross-link {
  display: block; margin: 6px 0 0; padding: 8px 11px;
  border-radius: var(--radius-sm); border: 1px dashed var(--line-strong);
  color: var(--ink-muted); text-decoration: none; font-size: 13px;
}
.cross-link:hover { background: var(--surface-sunk); color: var(--ink); }
body.admin .cross-link { border-color: #2b2f37; color: #8b909a; }
body.admin .cross-link:hover { background: #1d2027; color: #fff; }

/* ------------------------------------------------------ display typography

   The serif is for the things read at a glance: page titles, the figure on a
   card, the price on a tier. Everything a person scans rather than reads —
   labels, table cells, buttons — stays sans, where a serif costs legibility at
   small sizes and gains nothing. */

.panel h2, .panel h3 {
  font-family: var(--serif); font-size: 16px; font-weight: 550;
  letter-spacing: -.012em; text-transform: none; color: var(--ink);
}
.tier h2, .tier h3 { font-family: var(--serif); font-size: 17px; font-weight: 560; }
.tier-price strong { font-family: var(--serif); font-size: 30px; font-weight: 550; }
.shopfront h1 { font-size: 40px; line-height: 1.14; letter-spacing: -.022em; }
.shopfront .lede { font-size: 16px; }
.login h1 { font-family: var(--serif); font-size: 24px; font-weight: 560; }
.account-head strong { font-family: var(--serif); font-size: 16px; font-weight: 560; }

/* Big totals on a chart panel read as figures, so they get the serif too. */
.panel-chart .total { font-size: 13.5px; }

/* Softer, larger surfaces throughout. */
table { border-radius: var(--radius); }
.filters-panel { border-radius: var(--radius); }
.pill { border-radius: var(--radius-pill); }

/* A little more air between the sections of a page. */
.cards { gap: 18px; }
.chart-grid { gap: 20px; }
.chart-grid + .chart-grid { margin-top: 20px; }

/* ------------------------------------------------------------ colour with a job

   Tints say what a figure is ABOUT, and are used nowhere else. Green is money
   and good news, amber is something wanting attention, and the cool tints
   separate counts of different things from one another. A chart series never
   takes one: a line that changed colour would look like it had changed
   meaning. */

.card-chip {
  width: 34px; height: 34px; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  margin-bottom: 13px;
}
.card-chip svg { width: 17px; height: 17px; }
.chip-green  { background: var(--t-green-sunk);  color: var(--t-green); }
.chip-blue   { background: var(--t-blue-sunk);   color: var(--t-blue); }
.chip-violet { background: var(--t-violet-sunk); color: var(--t-violet); }
.chip-amber  { background: var(--t-amber-sunk);  color: var(--t-amber); }
.chip-teal   { background: var(--t-teal-sunk);   color: var(--t-teal); }
.chip-rose   { background: var(--t-rose-sunk);   color: var(--t-rose); }

/* A card that wants attention says so with its chip and its figure, not by
   shouting in a colour across the whole panel. */
.card.warn .card-chip { background: var(--t-amber-sunk); color: var(--t-amber); }

/* The striking green, kept for the one action a page most wants taken. Dark ink
   on lime: the reverse fails contrast badly and would be unreadable. */
.button.pop, button.pop {
  background: var(--pop); color: var(--pop-ink); font-weight: 620;
  border: 1px solid #b6e239;
}
.button.pop:hover, button.pop:hover { background: #d3f76a; }

/* Buttons follow the accent, so they went green with the palette. Give the
   primary one a little more presence. */
button, .button, .form button { font-weight: 550; }
.button:hover, button:hover { filter: brightness(1.04); }
button.danger, .button.danger { background: var(--danger); }

/* Tabs and segmented controls pick up the accent's tint rather than a grey. */
.segmented a.on { background: var(--accent-sunk); color: var(--accent-ink); }

/* ------------------------------------------------------------ revenue layout

   One page built around one figure. The chart is the point, so it gets the
   width; the controls move to a rail beside it where they are available
   without competing. */

.rev-layout { display: grid; grid-template-columns: minmax(0, 1fr) 268px; gap: 22px; align-items: start; }
.rev-main { min-width: 0; }

.filter-rail {
  position: sticky; top: 20px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 18px 18px 16px;
}
.filter-stack { display: flex; flex-direction: column; gap: 14px; align-items: stretch; }
.filter-stack .segmented { display: grid; grid-template-columns: repeat(2, 1fr); width: 100%; }
.filter-stack .dropdown { width: 100%; }
.filter-stack .dropdown > summary { width: 100%; }
.filter-stack .toggle { width: 100%; justify-content: flex-start; }
.filter-stack .cal { width: 100%; }
.filter-rail .hint { margin: 14px 0 0; font-size: 12px; }

/* The hero chart: the same drawing, given twice the height. */
.panel-chart.hero { margin-bottom: 4px; }
.panel-chart.hero .chart { margin-top: 8px; }

@media (max-width: 1100px) {
  .rev-layout { grid-template-columns: minmax(0, 1fr); }
  .filter-rail { position: static; order: -1; }
  .filter-stack { flex-direction: row; flex-wrap: wrap; }
  .filter-stack .segmented, .filter-stack .dropdown, .filter-stack .toggle { width: auto; }
  /* A flex row would squeeze the calendar to the width of its widest number.
     It has a grid inside it that has to keep seven readable columns, so it
     takes a fixed width here and does not shrink. */
  .filter-stack .cal { width: 252px; flex: none; }
}

/* Sub-tabs within a section. Links, not controls: each is a real page with its
   own address, so it can be bookmarked and the back button behaves. */
.tabs {
  display: flex; gap: 4px; margin: 0 0 26px;
  border-bottom: 1px solid var(--line);
}
.tabs a {
  padding: 9px 14px 11px; margin-bottom: -1px;
  color: var(--ink-2); text-decoration: none; font-size: 14px;
  border-bottom: 2px solid transparent;
}
.tabs a:hover { color: var(--ink); }
.tabs a.on { color: var(--accent-ink); border-bottom-color: var(--accent); font-weight: 580; }

/* ------------------------------------------------------------------- bots */

/* ---- bot cards

   These are the biggest thing on their page and are meant to be: a workspace
   has one or two bots, they are what subscribers actually talk to, and the
   page's whole job is to say which ones exist and whether they are working.
   Cards the size of a table row said none of that at a glance. */

/* auto-FILL, not auto-fit: with one bot connected, auto-fit collapses the empty
   tracks and stretches that single card across the whole page like a banner.
   auto-fill keeps the tracks, so one card is a card. */
.bot-cards {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(272px, 1fr)); gap: 18px;
  margin-bottom: 6px;
}
.bot-card {
  display: flex; flex-direction: column; align-items: flex-start; gap: 0;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 22px 24px 20px; box-shadow: var(--shadow);
  text-decoration: none; color: var(--ink); min-height: 168px;
  transition: box-shadow .15s ease, border-color .15s ease, transform .15s ease;
}
.bot-card:hover {
  box-shadow: var(--shadow-lift); border-color: var(--line-strong);
  transform: translateY(-1px);
}
.bot-card .card-chip { width: 40px; height: 40px; border-radius: 12px; margin-bottom: 16px; }
.bot-card .card-chip svg { width: 20px; height: 20px; }
.bot-name { display: block; }
.bot-card strong {
  display: block; font-family: var(--serif); font-size: 21px; font-weight: 560;
  letter-spacing: -.015em; line-height: 1.2;
}
.bot-user { display: block; font-size: 13px; color: var(--ink-muted); margin-top: 3px; }
/* The status sits on the bottom edge whatever the name's length, so a row of
   cards has its pills in a line. */
.bot-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: auto; padding-top: 16px; width: 100%;
}
.bot-meta { font-size: 13px; color: var(--ink-2); margin: 0; }
.bot-foot .pill { margin-left: auto; }

/* The whole page when there is nothing on it. One invitation reads better than
   two empty sections with a heading each. */
.empty-cta {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 44px 32px; text-align: center;
}
.empty-cta h2 {
  margin: 0 0 8px; font-family: var(--serif); font-size: 20px; font-weight: 560;
  text-transform: none; letter-spacing: -.012em; color: var(--ink);
}
.empty-cta p { margin: 0 auto 22px; color: var(--ink-2); font-size: 14px; max-width: 46ch; }

/* A numbered set of instructions that are steps to follow, not a wizard. */
.how-list { margin: 0 0 4px; padding-left: 22px; font-size: 13.5px; line-height: 1.75; }
.how-list li { margin-bottom: 4px; color: var(--ink-2); }
.how-list strong { color: var(--ink); font-weight: 600; }
.how-list code {
  background: var(--surface-sunk); border: 1px solid var(--line); border-radius: 5px;
  padding: 1px 5px; font-size: 12.5px;
}
/* A count on a navigation row, for the one thing that is genuinely waiting. */
.nav-badge {
  margin-left: auto; min-width: 19px; padding: 1px 6px; border-radius: var(--radius-pill);
  background: var(--warn-sunk); color: var(--warn-ink);
  font-size: 11px; font-weight: 650; text-align: center;
}
.side a.on .nav-badge { background: var(--warn); color: #fff; }

/* ---------------------------------------------------------------- a thread

   Who said a thing matters as much as what was said: the bot speaks in the
   operator's name, so its words are marked as its own rather than blending in
   with a colleague's. */
.thread { margin: 0 0 22px; display: flex; flex-direction: column; gap: 12px; max-width: 760px; }
.turn { padding: 12px 15px; border-radius: var(--radius); border: 1px solid var(--line); }
.turn-who { font-size: 11.5px; color: var(--ink-muted); margin-bottom: 5px; display: flex; gap: 8px; }
.turn-at { margin-left: auto; font-variant-numeric: tabular-nums; }
.turn-body { font-size: 14px; white-space: pre-wrap; }
.turn-subscriber { background: var(--surface); }
.turn-ai { background: var(--accent-sunk); border-color: transparent; margin-left: 40px; }
.turn-agent { background: var(--surface-sunk); margin-left: 40px; }
.turn-error { margin-top: 7px; font-size: 12px; color: var(--danger-ink); }

tr.row-urgent td:first-child { box-shadow: inset 3px 0 0 var(--warn); }

/* ------------------------------------------------------------------- tags

   A tag is a category, and the categorical tints are exactly for saying which
   category something belongs to. This is the second place they are allowed —
   the first being the icon chip on a card — and for the same reason: the colour
   names a kind, it does not rank or warn. */

.tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: var(--radius-pill);
  font-size: 12.5px; font-weight: 560; line-height: 1.5;
  white-space: nowrap;
}
.tag.small { font-size: 11.5px; padding: 1px 8px; font-weight: 550; }
.tag.t-green  { background: var(--t-green-sunk);  color: var(--t-green); }
.tag.t-blue   { background: var(--t-blue-sunk);   color: var(--t-blue); }
.tag.t-violet { background: var(--t-violet-sunk); color: var(--t-violet); }
.tag.t-amber  { background: var(--t-amber-sunk);  color: var(--t-amber); }
.tag.t-rose   { background: var(--t-rose-sunk);   color: var(--t-rose); }
.tag.t-teal   { background: var(--t-teal-sunk);   color: var(--t-teal); }
.tag.t-grey   { background: var(--surface-sunk);  color: var(--ink-2); }

/* Chips beside a name in the list. They sit under the name rather than after
   it, so a row with four tags does not push the columns out of line. */
.tag-row { display: block; margin-top: 4px; }
.tag-row .tag { margin-right: 4px; }

/* ---- the tags page */

.tag-list { display: flex; flex-direction: column; gap: 14px; margin-bottom: 34px; }
.tag-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px 20px 14px;
}
.tag-card > header { display: flex; align-items: center; gap: 12px; }
.tag-count { margin-left: auto; font-size: 12.5px; color: var(--ink-2); text-decoration: none; }
.tag-count:hover { color: var(--accent-ink); text-decoration: underline; }
.tag-desc { margin: 8px 0 0; color: var(--ink-2); font-size: 13px; }

.rule-list { list-style: none; margin: 12px 0 0; padding: 0; }
.rule-list li {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding: 8px 0; border-top: 1px solid var(--line); font-size: 13px;
}
.rule-what { font-weight: 550; }
.rule-qual {
  padding: 1px 8px; border-radius: var(--radius-pill);
  background: var(--surface-sunk); color: var(--ink-2); font-size: 12px;
}
.rule-matches { margin-left: auto; color: var(--ink-muted); font-size: 12.5px; }
.rule-empty { margin: 10px 0 0; color: var(--ink-muted); font-size: 13px; }

.rule-add { margin-top: 12px; }
.rule-add > summary {
  cursor: pointer; font-size: 13px; color: var(--accent-ink); font-weight: 550;
  list-style: none; padding: 4px 0;
}
.rule-add > summary::-webkit-details-marker { display: none; }
.rule-add > summary::before { content: "+ "; }
.rule-add[open] > summary::before { content: "− "; }
.rule-add form { margin-top: 12px; }
.rule-fields {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px; margin: 4px 0 18px;
}
.rule-fields label { margin: 0; }

/* Colour is picked by looking at it, so the swatch shows the colour rather than
   naming it in a dropdown. */
.swatches { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 4px; }
.swatch { display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer; }
.swatch input { position: absolute; opacity: 0; width: 0; height: 0; }
.swatch > span {
  width: 30px; height: 30px; border-radius: 50%;
  border: 2px solid transparent; box-shadow: inset 0 0 0 1px var(--line);
}
.swatch small { font-size: 10.5px; color: var(--ink-muted); }
.swatch input:checked + span { border-color: var(--ink); }
.swatch input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.swatch.t-green  > span { background: var(--t-green); }
.swatch.t-blue   > span { background: var(--t-blue); }
.swatch.t-violet > span { background: var(--t-violet); }
.swatch.t-amber  > span { background: var(--t-amber); }
.swatch.t-rose   > span { background: var(--t-rose); }
.swatch.t-teal   > span { background: var(--t-teal); }
.swatch.t-grey   > span { background: var(--line-strong); }

/* ---- tags on one person */

.tag-strip { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 22px; }
.tag-none { color: var(--ink-muted); font-size: 13px; }
.tag-x {
  background: none; border: 0; padding: 0; margin: 0 -2px 0 0; cursor: pointer;
  color: inherit; font-size: 14px; line-height: 1; opacity: .55;
}
.tag-x:hover { opacity: 1; }
.tag-add > summary { font-size: 12.5px; }
.dd-form { margin: 0; }
.dd-panel .dd-form button {
  display: block; width: 100%; text-align: left;
  padding: 7px 11px; font-size: 13px;
}

/* A button that is really a link: used where the action needs a POST — because
   it changes something — but should not look like a call to action. */
.link-button {
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  color: var(--ink-muted); font: inherit; font-size: 12.5px; text-decoration: underline;
  text-underline-offset: 2px; border-radius: 0;
}
.link-button:hover { color: var(--ink); filter: none; }
.link-button.danger-text { color: var(--danger-ink); }

/* Prose that explains how something behaves, set apart from the interface it
   describes. */
.notes {
  max-width: 72ch; border-left: 2px solid var(--line-strong); padding-left: 18px;
  color: var(--ink-2); font-size: 13.5px;
}
.notes p { margin: 0 0 12px; }
.notes strong { color: var(--ink); font-weight: 580; }

.form.narrow { max-width: 460px; }

/* --------------------------------------------------------- checkout pages

   A plan's public address, shown to the operator. It is a thing to be copied,
   so it is set in the code face and given room to be selected. */

.shop-bar {
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 16px 20px; margin: 0 0 20px;
}
.shop-bar strong { font-family: var(--serif); font-size: 15px; font-weight: 560; }
.shop-bar .hint { margin: 3px 0 0; }
.shop-link {
  margin-left: auto; font-family: var(--mono); font-size: 12.5px;
  background: var(--surface-sunk); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 7px 11px;
  color: var(--accent-ink); text-decoration: none; word-break: break-all;
}
.shop-link:hover { border-color: var(--accent); background: var(--accent-sunk); }
.shop-link.small { margin: 0; padding: 3px 8px; font-size: 11.5px; }

.address-form { max-width: 560px; }
.address-row { display: flex; align-items: stretch; }
.address-base {
  display: flex; align-items: center; padding: 0 10px;
  background: var(--surface-sunk); border: 1px solid var(--line-strong); border-right: 0;
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  font-family: var(--mono); font-size: 12px; color: var(--ink-muted); white-space: nowrap;
}
.address-row input { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; font-family: var(--mono); }

/* ------------------------------------------------ questions that depend on

   A one-time payment has no billing period, so the form stops asking for one.
   Done with :has() rather than script: these pages forbid script entirely, and
   a browser without :has() simply shows the fields, which the server ignores
   for that kind anyway. */

.form:has(input[name="kind"][value="lifetime"]:checked) .period-only { display: none; }

/* Back is not a destructive action. It was wearing the colour that means
   "this spends money or deletes something". */
.wizard-nav .button.ghost, .wizard-nav button.ghost {
  background: var(--surface); border: 1px solid var(--line-strong); color: var(--ink-2);
}
.wizard-nav button.ghost:hover { background: var(--surface-sunk); color: var(--ink); filter: none; }

/* Paid, and not collected. Amber because it wants attention rather than
   because anything is broken: the money arrived, the person just has not said
   which Telegram account is theirs. */
.unclaimed {
  background: var(--warn-sunk); border: 1px solid #f0d5a8;
  border-radius: var(--radius); padding: 16px 18px 8px; margin: 0 0 26px;
}
.unclaimed-head { margin: 0 0 12px; font-size: 13.5px; line-height: 1.55; color: var(--warn-ink); }
.unclaimed-head strong { color: var(--warn-ink); }
.unclaimed table { margin: 0 0 10px; box-shadow: none; }
.pill.warn { background: var(--warn-sunk); color: var(--warn-ink); }

/* ------------------------------------------------------- storefront settings */

.swatch.preset > span { box-shadow: inset 0 0 0 1px rgba(0,0,0,.12); }
.accent-0f9d63 > span { background: #0f9d63; }
.accent-2a78d6 > span { background: #2a78d6; }
.accent-6f4fd8 > span { background: #6f4fd8; }
.accent-d0453b > span { background: #d0453b; }
.accent-e8912a > span { background: #e8912a; }
.accent-0e9aa7 > span { background: #0e9aa7; }
.accent-1f2937 > span { background: #1f2937; }
.accent-c2185b > span { background: #c2185b; }

.hex-field { display: block; margin-top: 18px; max-width: 320px; }
.hex-field input { font-family: var(--mono); }

.logo-row { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-bottom: 10px; }
.logo-preview {
  height: 40px; width: auto; max-width: 220px; object-fit: contain;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 6px 10px;
}
.logo-none { color: var(--ink-muted); font-size: 13px; }
.logo-form { display: flex; align-items: center; gap: 10px; margin: 0; }
.logo-form input[type="file"] { font-size: 12.5px; color: var(--ink-2); }
.logo-form button { margin: 0; }

/* ------------------------------------------------------ revenue, rearranged

   Three figures answer the page's question — what came in, what went back,
   what is left — and everything else supports them. So the three are large and
   the rest are small, because nine equally loud numbers have no headline. */

.cards.headline { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); margin-bottom: 14px; }
.card.big { padding: 22px 24px 20px; }
.card.big .l { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; }
.card.big .n { font-size: 36px; }
.cards.small { grid-template-columns: repeat(auto-fit, minmax(152px, 1fr)); gap: 12px; }
.cards.small .card { padding: 14px 16px 12px; }
.cards.small .n { font-size: 20px; }
.cards.small .sub { font-size: 11px; }

.rail-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .085em;
  color: var(--ink-muted); font-weight: 650; margin-top: 4px;
}
.filter-stack .rail-label:first-child { margin-top: 0; }

/* ------------------------------------------------------------- page heading

   The title on the left, and on the right the one thing the page most wants
   done — "New plan", "Add a bot". Every list page in the workspace is built
   this way, so the control that makes a new thing is always in the same place
   and always looks the same. Before this it was sometimes a button under the
   lede, sometimes a whole form at the bottom of the page. */

.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 24px; flex-wrap: wrap; margin-bottom: 30px;
}
.page-head .page-title { flex: 1 1 380px; min-width: 0; }
.page-head h1 { margin-bottom: 8px; }
.page-head .lede { margin-bottom: 0; }
/* Nudged down to sit on the title's baseline rather than its box. */
.page-head > .button { flex: none; margin-top: 6px; }
/* A page with tabs under the heading keeps them full width below both. */
.page-head + .tabs { margin-top: -14px; }

/* Fields that only belong to one answer above them. Both are written out and
   the stylesheet hides the one that does not apply — the same :has() trick the
   plan and automation forms use, and the reason those forms need no script. */
.form .if-amount, .form:has(input[name="kind"][value="amount"]:checked) .if-percent {
  display: none;
}
.form:has(input[name="kind"][value="amount"]:checked) .if-amount { display: block; }
.form .if-by-tag { display: none; margin-top: 14px; }
.form:has(input[name="audience"][value="tag"]:checked) .if-by-tag { display: block; }
.form .if-some-plans { display: none; margin-top: 14px; }
.form:has(input[name="scope"][value="some"]:checked) .if-some-plans { display: block; }

.coupon-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
  background: var(--surface-sunk); border: 1px solid var(--line);
  border-radius: 6px; padding: 2px 7px; letter-spacing: .02em; font-weight: 600;
}
/* Two one-button forms in a table cell, side by side rather than stacked. */
.row-actions { display: flex; gap: 12px; justify-content: flex-end; align-items: baseline; }
.row-actions form { margin: 0; }
.row-actions button { white-space: nowrap; }

/* The period control on a page with no filter rail to put it in. A row above
   the figures it governs, with the dates it resolves to stated beside it: a
   preset that says "30 days" and nothing else leaves somebody counting back. */
.period-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 0 0 18px;
}
.period-bar .rail-label { margin: 0; }
.period-span { font-size: 12.5px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.period-cal { max-width: 264px; margin: -6px 0 20px; }

/* ---- the date-range calendar

   Drawn here rather than by the browser, and driven with no script at all: two
   clicks with a page load between them, and the band between the first click
   and the cursor painted by sibling selectors.

   The trick is subtraction the long way round. Every day after the anchor is
   painted in, and then everything after the CURSOR is painted back out — which
   leaves exactly the days between the two. SOURCE ORDER IS LOAD-BEARING: the
   two rules have equal specificity, so the second only wins by coming after.
   Do not reorder them, and do not raise the specificity of the first. */

.cal {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); padding: 10px 10px 4px; user-select: none;
}
.cal-head { display: flex; align-items: center; justify-content: space-between; gap: 4px; }
.cal-title { font-size: 13px; font-weight: 600; letter-spacing: -.01em; }
.cal-arrow {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 7px; color: var(--ink-2);
  text-decoration: none; flex: none;
}
.cal-arrow:hover { background: var(--surface-sunk); color: var(--ink); }
.cal-arrow.off { visibility: hidden; }

.cal-dow, .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.cal-dow {
  margin-top: 8px; font-size: 10.5px; font-weight: 650; letter-spacing: .04em;
  color: var(--ink-muted); text-align: center;
}
.cal-dow span { padding: 4px 0; }

/* No gap between cells: a range is one continuous band, and a gutter would cut
   it into squares. It also keeps the cursor inside a cell as it crosses the
   grid, so the preview does not flicker between them. */
.cal-grid { margin-top: 2px; }
.cal .d, .cal .pad {
  display: flex; align-items: center; justify-content: center;
  height: 30px; font-size: 12.5px; font-variant-numeric: tabular-nums;
  color: var(--ink-2); text-decoration: none;
}
.cal .d { border-radius: 8px; }
.cal .d.out { color: var(--ink-muted); opacity: .4; pointer-events: none; }
.cal .d.today { font-weight: 700; color: var(--ink); }
.cal:not(.picking) .d:hover { background: var(--surface-sunk); color: var(--ink); }

/* The range already in force. Square in the middle, capped at the ends. */
.cal .d.in { background: var(--cal-band); border-radius: 0; color: var(--ink); }
.cal .d.sel-from, .cal .d.sel-to {
  background: var(--cal-edge); color: var(--cal-edge-ink); font-weight: 600;
}
.cal .d.sel-from { border-radius: 8px 0 0 8px; }
.cal .d.sel-to { border-radius: 0 8px 8px 0; }
.cal .d.sel-from.sel-to { border-radius: 8px; }

/* Mid-choice: the band from the first click to wherever the cursor is.

   Each pair of rules below is the same idea — paint a range in, then take the
   part past the cursor back out, which leaves exactly the days between. Only
   the direction differs, and the direction is settled in the markup: the server
   marks every day .pre or .post according to which side of the anchor it falls
   on, because a sibling selector can reach the elements AFTER one but never the
   ones before it. That also carries a band across months at no extra cost — in
   a month before the first click every day is .pre, in a later one every day is
   .post, and the same two pairs cover both.

   All of it is gated on :has(…:hover), so nothing is painted until the cursor
   is actually over a day. Without that the rest of the month lights up the
   instant the first day is clicked, which reads as a range nobody asked for.

   SPECIFICITY IS LOAD-BEARING. Within each pair the rule that takes back has to
   outrank the rule that paints, and the weights below are what makes that true.
   Simplifying a selector here changes which one wins. */

/* Cursor after the anchor: paint past the anchor, take back past the cursor. */
.cal-grid:has(.post:hover) .d.post { background: var(--cal-band); border-radius: 0; }
.cal-grid:has(.post:hover) .d:hover ~ .d { background: transparent; border-radius: 8px; }
.cal-grid:has(.post:hover) .d.post:hover {
  background: var(--cal-band); border-radius: 0 8px 8px 0;
}

/* Cursor before the anchor: paint past the cursor, take back past the anchor. */
.cal-grid:has(.pre:hover) .d:hover ~ .d { background: var(--cal-band); border-radius: 0; }
.cal-grid:has(.pre:hover) .d:hover ~ .d.post { background: transparent; border-radius: 8px; }
.cal-grid:has(.pre:hover) .d.pre:hover {
  background: var(--cal-band); border-radius: 8px 0 0 8px;
}

/* The first click keeps its own fill whichever way the band runs around it. */
.cal .d.anchor {
  background: var(--cal-edge); color: var(--cal-edge-ink); font-weight: 600;
  border-radius: 8px;
}
.cal-grid:has(.post:hover) .d.anchor { border-radius: 8px 0 0 8px; }
.cal-grid:has(.pre:hover) .d:hover ~ .d.anchor {
  background: var(--cal-edge); color: var(--cal-edge-ink); border-radius: 0 8px 8px 0;
}

.cal-hint {
  margin: 8px 0 6px; font-size: 11.5px; color: var(--ink-muted); text-align: center;
}
.cal-hint a { color: var(--ink-2); margin-left: 6px; }

/* A dropdown entry that carries the dates it resolves to underneath its name. */
.dd-panel a.two-line { white-space: normal; line-height: 1.35; }
.dd-panel a.two-line small {
  display: block; font-size: 11.5px; color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.dd-panel a.two-line.on small { color: inherit; opacity: .75; }

/* Three choices in the rail, where the default is two columns. */
.filter-stack .segmented.three { grid-template-columns: repeat(3, 1fr); }

/* Money leaving rather than arriving.

   The rule elsewhere is that a chart series never takes a tint, so a line that
   changed colour cannot look like it changed meaning. This is the exception
   that proves it: here the meaning HAS changed — the chart is drawing refunds,
   not takings — and red already means money going the wrong way everywhere
   else on this page. It only ever applies when the filter says refunds alone. */
.panel-chart.outgoing .line-mark { stroke: var(--danger); }
.panel-chart.outgoing .line-area { fill: var(--danger-sunk); }
.panel-chart.outgoing .pt .dot { stroke: var(--danger); }
.panel-chart.outgoing .pt .guide { stroke: var(--danger); }
.panel-chart.outgoing .pt-muted .dot { stroke: var(--danger); opacity: 0; }
.panel-chart.outgoing .bar-fill { fill: var(--danger); }

.segmented.tiny { margin-left: auto; }
.segmented.tiny a { padding: 4px 9px; font-size: 11.5px; }

/* ---- rings

   Colour is categorical here, and this is the one place on a chart where that
   is right: a ring has no series, only categories, and telling them apart is
   the whole job. */

.donut-wrap { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; margin-top: 6px; }
.donut { width: 168px; height: 168px; flex: none; }
.donut-track { stroke: var(--surface-sunk); }
.donut-slice { transition: opacity .12s ease; cursor: default; }
.donut-wrap:hover .donut-slice { opacity: .45; }
.donut-wrap .donut-slice:hover { opacity: 1; }

.donut-key { list-style: none; margin: 0; padding: 0; flex: 1; min-width: 190px; }
.donut-key li {
  display: grid; grid-template-columns: 12px 1fr auto auto; align-items: baseline;
  gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 13px;
}
.donut-key li:last-child { border-bottom: 0; }
.key-dot { width: 9px; height: 9px; border-radius: 3px; align-self: center; }
.key-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.key-share { color: var(--ink-muted); font-variant-numeric: tabular-nums; font-size: 12px; }
.key-note { font-variant-numeric: tabular-nums; font-weight: 540; }
.key-green  { background: var(--t-green); }
.key-blue   { background: var(--t-blue); }
.key-violet { background: var(--t-violet); }
.key-amber  { background: var(--t-amber); }
.key-teal   { background: var(--t-teal); }
.key-rose   { background: var(--t-rose); }
.key-other  { background: var(--line-strong); }

/* ---------------------------------------------------------------- dark mode

   One block of tokens, applied two ways: to anybody who asked for dark, and to
   anybody who asked for nothing and whose system is dark. The `:not(.light)`
   is what lets an explicit choice of light survive a dark system — without it
   the media query would quietly overrule the person.

   Only the neutrals move. Green still means money, amber still means attention
   and red still means something went wrong; a status colour that changed
   meaning with the lights would be a worse bug than a dim page. What changes
   is the WASH behind each of them, which has to be dark to sit on a dark card
   and light to sit on a light one. */

body.dark,
body:not(.light) {
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  body:not(.light):not(.store):not(.site) { color-scheme: dark; }
}

body.dark {
  color-scheme: dark;
  --flow-dot:     #2a2a28;
  --flow-line:    #3d3d3a;
  --gate-ground:  #0f100f;
  --gate-glow-a:  rgba(43,184,124,.24);
  --gate-glow-b:  rgba(169,216,56,.16);
  --plane:        #121211;
  --surface:      #1b1b1a;
  --surface-sunk: #232322;
  --rail:         #1b1b1a;

  --ink:          #f4f3f0;
  --ink-2:        #b9b7b1;
  --ink-muted:    #8b8983;

  --line:         #2c2c2a;
  --line-strong:  #3c3c39;

  /* The accent lifts: #0f9d63 on near-black is legible but heavy, and the same
     green a few steps brighter reads as the same colour without the weight. */
  --accent:       #2bb87c;
  --accent-sunk:  #14322a;
  --accent-ink:   #63d3a2;
  --accent-deep:  #1f9d68;

  --good:         #2bb87c;  --good-sunk:   #14322a;  --good-ink:   #63d3a2;
  --warn:         #e8a13f;  --warn-sunk:   #3a2a12;  --warn-ink:   #f0c07c;
  --danger:       #e0655a;  --danger-sunk: #3a1a18;  --danger-ink: #f09b92;

  --t-green:  #2bb87c;  --t-green-sunk:  #14322a;
  --t-blue:   #5b9ff0;  --t-blue-sunk:   #16263c;
  --t-violet: #9a80ee;  --t-violet-sunk: #241f3d;
  --t-amber:  #e8a13f;  --t-amber-sunk:  #3a2a12;
  --t-rose:   #e0655a;  --t-rose-sunk:   #3a1a18;
  --t-teal:   #35b6c2;  --t-teal-sunk:   #102f33;

  --grid:     #262624;
  --baseline: #3c3c39;

  /* Lifted off the card and outlined, rather than a black panel on a
     near-black chart where its own edges would be invisible. */
  --tip-bg:   #343431;
  --tip-edge: #56564f;
  --tip-ink:  #f4f3f0;
  --tip-sub:  rgba(244,243,240,.7);
  --cal-band:     #1b3d31;
  --cal-edge:     #2bb87c;
  --cal-edge-ink: #0d2b20;

  --shadow:      0 1px 2px rgba(0,0,0,.32), 0 4px 16px rgba(0,0,0,.28);
  --shadow-lift: 0 2px 4px rgba(0,0,0,.36), 0 10px 30px rgba(0,0,0,.42);

  background: var(--plane);
  color: var(--ink);
}

/* The same tokens again for "follow my system". Written out rather than shared
   with a class, because a media query cannot be aliased and a preprocessor is
   not something this project has. */
@media (prefers-color-scheme: dark) {
  /* The dashboard and its sign-in pages follow the system. The public site and
     a tenant's shop do not: those carry their own light-or-dark decision — ours
     on the landing page, the seller's on theirs — and a visitor's machine does
     not get to overrule either. */
  body:not(.light):not(.store):not(.site) {
  --flow-dot:     #2a2a28;
  --flow-line:    #3d3d3a;
  --gate-ground:  #0f100f;
  --gate-glow-a:  rgba(43,184,124,.24);
  --gate-glow-b:  rgba(169,216,56,.16);
    --plane:        #121211;
    --surface:      #1b1b1a;
    --surface-sunk: #232322;
    --rail:         #1b1b1a;
    --ink:          #f4f3f0;
    --ink-2:        #b9b7b1;
    --ink-muted:    #8b8983;
    --line:         #2c2c2a;
    --line-strong:  #3c3c39;
    --accent:       #2bb87c;
    --accent-sunk:  #14322a;
    --accent-ink:   #63d3a2;
    --accent-deep:  #1f9d68;
    --good:         #2bb87c;  --good-sunk:   #14322a;  --good-ink:   #63d3a2;
    --warn:         #e8a13f;  --warn-sunk:   #3a2a12;  --warn-ink:   #f0c07c;
    --danger:       #e0655a;  --danger-sunk: #3a1a18;  --danger-ink: #f09b92;
    --t-green:  #2bb87c;  --t-green-sunk:  #14322a;
    --t-blue:   #5b9ff0;  --t-blue-sunk:   #16263c;
    --t-violet: #9a80ee;  --t-violet-sunk: #241f3d;
    --t-amber:  #e8a13f;  --t-amber-sunk:  #3a2a12;
    --t-rose:   #e0655a;  --t-rose-sunk:   #3a1a18;
    --t-teal:   #35b6c2;  --t-teal-sunk:   #102f33;
    --grid:     #262624;
    --baseline: #3c3c39;

    /* Lifted off the card and outlined, rather than a black panel on a
       near-black chart where its own edges would be invisible. */
    --tip-bg:   #343431;
    --tip-edge: #56564f;
    --tip-ink:  #f4f3f0;
    --tip-sub:  rgba(244,243,240,.7);
    --cal-band:     #1b3d31;
    --cal-edge:     #2bb87c;
    --cal-edge-ink: #0d2b20;
    --shadow:      0 1px 2px rgba(0,0,0,.32), 0 4px 16px rgba(0,0,0,.28);
    --shadow-lift: 0 2px 4px rgba(0,0,0,.36), 0 10px 30px rgba(0,0,0,.42);
    background: var(--plane);
    color: var(--ink);
  }
}

/* The lime keeps dark ink in both schemes — it is a light colour either way. */
body.dark .button.pop, body.dark button.pop { border-color: #a9d838; }

/* Inputs and tables are drawn from the surface tokens, so most of the interface
   follows on its own. These are the few places a literal colour had crept in. */
body.dark input, body.dark select, body.dark textarea,
body:not(.light) input, body:not(.light) select, body:not(.light) textarea {
  background: var(--surface-sunk); color: var(--ink);
}
body.dark .unclaimed { border-color: #4a3a1c; }

/* The rail's brand block is a link now. It must not look like one. */
.side a.brand { text-decoration: none; color: inherit; border-radius: var(--radius-sm); }
.side a.brand:hover { background: var(--surface-sunk); }

/* --------------------------------------------------------------- workspaces */

.workspace-list { list-style: none; margin: 0 0 22px; padding: 0; }
.workspace-list li {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 18px; margin-bottom: 9px;
}
.workspace-list li.here { border-color: var(--accent); background: var(--accent-sunk); }
.ws-name { font-family: var(--serif); font-size: 16px; font-weight: 560; }
.ws-meta { color: var(--ink-muted); font-size: 12.5px; }
.workspace-list form, .ws-here { margin-left: auto; }
.ws-here { font-size: 12.5px; color: var(--accent-ink); font-weight: 560; }
.workspace-list button { margin: 0; }

/* The rail's switcher. A disclosure rather than a menu, so it needs no script. */
.brand-switch { position: relative; }
.brand-switch > summary { list-style: none; cursor: pointer; }
.brand-switch > summary::-webkit-details-marker { display: none; }
.brand-switch > summary:hover { background: var(--surface-sunk); border-radius: var(--radius-sm); }
.brand-switch .dd-panel {
  position: absolute; left: 4px; right: 4px; top: 100%; z-index: 30;
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-lift); padding: 5px;
}
.brand-switch .dd-panel a, .brand-switch .dd-panel button {
  display: block; width: 100%; text-align: left; padding: 7px 10px;
  font-size: 13px; text-decoration: none; color: var(--ink); border-radius: 6px;
}
.brand-switch .dd-panel a:hover, .brand-switch .dd-panel button:hover {
  background: var(--surface-sunk);
}

/* ------------------------------------------------- fields that follow a rule

   A rule's extra questions appear only for the answers that read them. Asking
   for a waiting period on "was refunded" is asking a question with no answer,
   and a form showing every question at once makes somebody work out which ones
   apply. :has() again — these pages allow no script. */

.rule-field { display: none; }

.form:has(input[name="event"][value="purchased"]:checked)            .needs-days,
.form:has(input[name="event"][value="subscription_expired"]:checked) .needs-days,
.form:has(input[name="event"][value="canceled"]:checked)             .needs-days,
.form:has(input[name="event"][value="checkout_abandoned"]:checked)   .needs-days,
.form:has(input[name="event"][value="no_purchase_yet"]:checked)      .needs-days,
.form:has(input[name="event"][value="purchased"]:checked)            .needs-plan,
.form:has(input[name="event"][value="checkout_abandoned"]:checked)   .needs-plan,
.form:has(input[name="event"][value="spent_over"]:checked)           .needs-amount {
  display: block;
}

/* A row of actions above a list — where "new one" belongs, rather than at the
   bottom under everything it would create. */
.page-actions { display: flex; justify-content: flex-end; gap: 10px; margin: 0 0 18px; }
.page-actions .button { margin: 0; }

.tag-form fieldset { margin-bottom: 20px; }

/* ------------------------------------------------------------- composing

   The form and the preview side by side. A message is written in markdown and
   arrives as something else; nobody should have to hold the difference in
   their head while also choosing an audience. */

.compose { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 24px; align-items: start; }
.compose .wizard { min-width: 0; }
.preview-pane { position: sticky; top: 20px; }

.phone {
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: 22px; box-shadow: var(--shadow-lift); padding: 12px 12px 26px;
}
.phone-head {
  font-size: 12px; color: var(--ink-muted); text-align: center;
  padding: 4px 0 12px; border-bottom: 1px solid var(--line); margin-bottom: 14px;
}
.bubble {
  background: var(--accent-sunk); border-radius: 14px 14px 14px 4px;
  padding: 11px 14px; font-size: 14px; line-height: 1.5; color: var(--ink);
  word-break: break-word;
}
.bubble b, .bubble strong { font-weight: 650; }
.bubble a { color: var(--accent-ink); }
.bubble code {
  background: var(--surface-sunk); border-radius: 4px; padding: 1px 4px; font-size: 12.5px;
}
.bubble-empty { color: var(--ink-muted); font-size: 13px; }

/* The audience's extra question, and the schedule's, appear only when the
   answer above them calls for one. */
.wizard:has(input[name="audience"][value="plan"]:checked) .needs-plan-audience,
.wizard:has(input[name="audience"][value="tag"]:checked)  .needs-tag-audience,
.wizard:has(input[name="when"][value="later"]:checked)    .needs-when {
  display: block;
}

@media (max-width: 1000px) {
  .compose { grid-template-columns: minmax(0, 1fr); }
  .preview-pane { position: static; order: -1; }
  .phone { max-width: 420px; }
}

/* ------------------------------------------------------------- the overview

   The page answers two questions, in this order: does anything need me, and
   how is it going. It used to answer nine at once, all at the same volume,
   which is a page nobody reads — the eye has nowhere to land first. */

.section-head {
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-muted); font-weight: 650; margin: 34px 0 14px;
}
h1 + .lede + .attention .section-head,
h1 + .lede + .all-clear + .headline-panel { margin-top: 0; }

/* ---- wants you */

.attention { margin-bottom: 26px; }
.attention-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
.attention-card {
  display: block; padding: 15px 18px 13px; text-decoration: none;
  background: var(--warn-sunk); border: 1px solid #f0d5a8; border-radius: var(--radius);
  color: var(--warn-ink);
}
body.dark .attention-card, body:not(.light) .attention-card { border-color: #4a3a1c; }
.attention-card:hover { border-color: var(--warn); }
.attention-card .n {
  display: block; font-family: var(--serif); font-size: 28px; font-weight: 550;
  line-height: 1.1; letter-spacing: -.02em;
}
.attention-card .l { display: block; font-size: 13px; font-weight: 550; margin-top: 3px; }
.attention-card .sub { display: block; font-size: 11.5px; opacity: .8; margin-top: 3px; }

.all-clear {
  margin: 0 0 26px; padding: 14px 18px; border-radius: var(--radius);
  background: var(--good-sunk); color: var(--good-ink); font-size: 13.5px; font-weight: 520;
}

/* ---- the one number */

.headline-panel {
  display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 26px; align-items: center;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 24px 26px;
}
.headline-figure .l {
  display: block; font-size: 12px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-muted); font-weight: 650;
}
.headline-figure .n {
  display: block; font-family: var(--serif); font-size: 40px; font-weight: 550;
  letter-spacing: -.026em; line-height: 1.1; margin: 6px 0 4px;
}
.headline-figure .sub { display: block; font-size: 12.5px; color: var(--ink-2); }
.headline-figure .button { margin: 16px 0 0; }
.headline-chart { min-width: 0; }

@media (max-width: 860px) {
  .headline-panel { grid-template-columns: minmax(0, 1fr); gap: 14px; }
}

/* Five small figures rather than nine large ones. */
.cards.glance { grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 12px; }

/* ------------------------------------------------------------ appearance

   Three small buttons at the foot of the rail: light, dark, and follow the
   system. A two-state toggle would have hidden the third answer, and "follow
   the system" is the default — a default nobody can see is a default nobody
   knows they are on. */

.theme-switch {
  display: flex; gap: 2px; margin: 0 0 8px; padding: 3px;
  background: var(--surface-sunk); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.theme-switch button {
  flex: 1; display: flex; align-items: center; justify-content: center;
  margin: 0; padding: 6px 0; border: 0; border-radius: 6px;
  background: none; color: var(--ink-muted); cursor: pointer;
}
.theme-switch button:hover { background: var(--surface); color: var(--ink); filter: none; }
.theme-switch button.on { background: var(--surface); color: var(--accent-ink); box-shadow: var(--shadow); }
body.admin .theme-switch { background: #16181d; border-color: #2b2f37; }
body.admin .theme-switch button { color: #8b909a; }
body.admin .theme-switch button.on { background: #232730; color: #fff; }

/* ---- labels on a customer

   A row of toggles rather than a dropdown and an Add button: there are few
   enough of them to show all at once, and which are ON is the thing being
   read. Off is an outline, on is filled — the same tints a subscriber tag
   uses, so one colour means one thing across the product. */
.tag-toggles { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 4px; }
.tag-toggles form { margin: 0; }
.tag-toggle {
  padding: 6px 12px; border-radius: var(--radius-pill); font-size: 12.5px;
  font-weight: 550; cursor: pointer; background: transparent;
  border: 1px solid var(--line-strong); color: var(--ink-2);
}
.tag-toggle:hover { border-color: var(--ink-muted); color: var(--ink); filter: none; }
.tag-toggle.on { border-color: transparent; }
.tag-toggle.on.t-green  { background: var(--t-green-sunk);  color: var(--t-green); }
.tag-toggle.on.t-blue   { background: var(--t-blue-sunk);   color: var(--t-blue); }
.tag-toggle.on.t-violet { background: var(--t-violet-sunk); color: var(--t-violet); }
.tag-toggle.on.t-amber  { background: var(--t-amber-sunk);  color: var(--t-amber); }
.tag-toggle.on.t-rose   { background: var(--t-rose-sunk);   color: var(--t-rose); }
.tag-toggle.on.t-teal   { background: var(--t-teal-sunk);   color: var(--t-teal); }
.tag-toggle.on.t-grey   { background: var(--surface-sunk);  color: var(--ink); }

/* ---- how much room is left

   What somebody wants from a billing page is not "you have three channels" but
   "you have one left" — the number that decides whether they have to act. The
   bar is drawn from a fixed set of width classes because the policy forbids a
   style attribute, so a computed percentage cannot travel in the markup. */
.room { list-style: none; margin: 10px 0 4px; padding: 0; }
.room-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 120px auto auto;
  align-items: center; gap: 14px; padding: 11px 0;
  border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.room-row:last-child { border-bottom: 0; }
.room-label { font-weight: 550; }
.room-bar {
  height: 6px; border-radius: 3px; background: var(--surface-sunk);
  overflow: hidden; display: block;
}
.room-bar i { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
.room-row.tight .room-bar i { background: var(--warn); }
.room-row.full .room-bar i { background: var(--danger); }
.room-count { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.room-left {
  font-variant-numeric: tabular-nums; font-weight: 600; text-align: right;
  min-width: 76px;
}
.room-row.tight .room-left { color: var(--warn-ink); }
.room-row.full .room-left { color: var(--danger-ink); }

@media (max-width: 620px) {
  .room-row { grid-template-columns: minmax(0, 1fr) auto; }
  .room-bar { grid-column: 1 / -1; }
}

/* One class per whole percent. Generated rather than clever: the content
   security policy discards a style attribute, so a computed width cannot
   travel in the markup and has to exist as a rule. */
.room-bar i.w-0 { width: 0%; }
.room-bar i.w-1 { width: 1%; }
.room-bar i.w-2 { width: 2%; }
.room-bar i.w-3 { width: 3%; }
.room-bar i.w-4 { width: 4%; }
.room-bar i.w-5 { width: 5%; }
.room-bar i.w-6 { width: 6%; }
.room-bar i.w-7 { width: 7%; }
.room-bar i.w-8 { width: 8%; }
.room-bar i.w-9 { width: 9%; }
.room-bar i.w-10 { width: 10%; }
.room-bar i.w-11 { width: 11%; }
.room-bar i.w-12 { width: 12%; }
.room-bar i.w-13 { width: 13%; }
.room-bar i.w-14 { width: 14%; }
.room-bar i.w-15 { width: 15%; }
.room-bar i.w-16 { width: 16%; }
.room-bar i.w-17 { width: 17%; }
.room-bar i.w-18 { width: 18%; }
.room-bar i.w-19 { width: 19%; }
.room-bar i.w-20 { width: 20%; }
.room-bar i.w-21 { width: 21%; }
.room-bar i.w-22 { width: 22%; }
.room-bar i.w-23 { width: 23%; }
.room-bar i.w-24 { width: 24%; }
.room-bar i.w-25 { width: 25%; }
.room-bar i.w-26 { width: 26%; }
.room-bar i.w-27 { width: 27%; }
.room-bar i.w-28 { width: 28%; }
.room-bar i.w-29 { width: 29%; }
.room-bar i.w-30 { width: 30%; }
.room-bar i.w-31 { width: 31%; }
.room-bar i.w-32 { width: 32%; }
.room-bar i.w-33 { width: 33%; }
.room-bar i.w-34 { width: 34%; }
.room-bar i.w-35 { width: 35%; }
.room-bar i.w-36 { width: 36%; }
.room-bar i.w-37 { width: 37%; }
.room-bar i.w-38 { width: 38%; }
.room-bar i.w-39 { width: 39%; }
.room-bar i.w-40 { width: 40%; }
.room-bar i.w-41 { width: 41%; }
.room-bar i.w-42 { width: 42%; }
.room-bar i.w-43 { width: 43%; }
.room-bar i.w-44 { width: 44%; }
.room-bar i.w-45 { width: 45%; }
.room-bar i.w-46 { width: 46%; }
.room-bar i.w-47 { width: 47%; }
.room-bar i.w-48 { width: 48%; }
.room-bar i.w-49 { width: 49%; }
.room-bar i.w-50 { width: 50%; }
.room-bar i.w-51 { width: 51%; }
.room-bar i.w-52 { width: 52%; }
.room-bar i.w-53 { width: 53%; }
.room-bar i.w-54 { width: 54%; }
.room-bar i.w-55 { width: 55%; }
.room-bar i.w-56 { width: 56%; }
.room-bar i.w-57 { width: 57%; }
.room-bar i.w-58 { width: 58%; }
.room-bar i.w-59 { width: 59%; }
.room-bar i.w-60 { width: 60%; }
.room-bar i.w-61 { width: 61%; }
.room-bar i.w-62 { width: 62%; }
.room-bar i.w-63 { width: 63%; }
.room-bar i.w-64 { width: 64%; }
.room-bar i.w-65 { width: 65%; }
.room-bar i.w-66 { width: 66%; }
.room-bar i.w-67 { width: 67%; }
.room-bar i.w-68 { width: 68%; }
.room-bar i.w-69 { width: 69%; }
.room-bar i.w-70 { width: 70%; }
.room-bar i.w-71 { width: 71%; }
.room-bar i.w-72 { width: 72%; }
.room-bar i.w-73 { width: 73%; }
.room-bar i.w-74 { width: 74%; }
.room-bar i.w-75 { width: 75%; }
.room-bar i.w-76 { width: 76%; }
.room-bar i.w-77 { width: 77%; }
.room-bar i.w-78 { width: 78%; }
.room-bar i.w-79 { width: 79%; }
.room-bar i.w-80 { width: 80%; }
.room-bar i.w-81 { width: 81%; }
.room-bar i.w-82 { width: 82%; }
.room-bar i.w-83 { width: 83%; }
.room-bar i.w-84 { width: 84%; }
.room-bar i.w-85 { width: 85%; }
.room-bar i.w-86 { width: 86%; }
.room-bar i.w-87 { width: 87%; }
.room-bar i.w-88 { width: 88%; }
.room-bar i.w-89 { width: 89%; }
.room-bar i.w-90 { width: 90%; }
.room-bar i.w-91 { width: 91%; }
.room-bar i.w-92 { width: 92%; }
.room-bar i.w-93 { width: 93%; }
.room-bar i.w-94 { width: 94%; }
.room-bar i.w-95 { width: 95%; }
.room-bar i.w-96 { width: 96%; }
.room-bar i.w-97 { width: 97%; }
.room-bar i.w-98 { width: 98%; }
.room-bar i.w-99 { width: 99%; }
.room-bar i.w-100 { width: 100%; }

/* ---- an invoice

   Something people put into their own bookkeeping, and the way everybody does
   that is print to PDF. So it is laid out as a document rather than as a panel,
   and the print rules take the interface away and leave the paper. */
.invoice {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 40px 44px; max-width: 720px; box-shadow: var(--shadow);
}
.invoice-head {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 24px; flex-wrap: wrap; padding-bottom: 24px; border-bottom: 1px solid var(--line);
}
.invoice-head h1 { margin: 0; font-size: 28px; }
.invoice-no {
  margin: 4px 0 0; font-variant-numeric: tabular-nums; color: var(--ink-muted);
  font-size: 13px; letter-spacing: .04em;
}
.invoice-from { text-align: right; display: flex; flex-direction: column; gap: 2px; }
.invoice-from strong { font-family: var(--serif); font-size: 16px; font-weight: 560; }
.invoice-from span { font-size: 12.5px; color: var(--ink-muted); }
.invoice-parties {
  display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 24px 0;
}
.invoice-parties > div { display: flex; flex-direction: column; gap: 2px; }
.invoice-label {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-muted); font-weight: 650; margin-top: 10px;
}
.invoice-parties > div > .invoice-label:first-child { margin-top: 0; }
.invoice-parties strong { font-weight: 550; font-size: 14px; }
.invoice-parties span:not(.invoice-label) { font-size: 13px; color: var(--ink-2); }
.invoice-lines { width: 100%; margin: 6px 0 0; }
.invoice-lines tfoot th { font-size: 15px; padding-top: 14px; }
.invoice-fine { margin: 26px 0 0; font-size: 12px; color: var(--ink-muted); }

@media print {
  .side, .plan-bar, .button.back, .theme-switch { display: none !important; }
  main { padding: 0; max-width: none; }
  .invoice { border: 0; box-shadow: none; padding: 0; max-width: none; }
}

/* ---- the settings hub

   One card per area of workspace housekeeping, each carrying the one fact that
   decides whether it needs attention — so the page answers "is anything wrong"
   without anybody opening a single card. */
.setting-cards {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 18px;
}
.setting-card {
  display: flex; flex-direction: column; align-items: flex-start;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 22px 24px 20px; box-shadow: var(--shadow);
  text-decoration: none; color: var(--ink); min-height: 172px;
  transition: box-shadow .15s ease, border-color .15s ease, transform .15s ease;
}
.setting-card:hover {
  box-shadow: var(--shadow-lift); border-color: var(--line-strong);
  transform: translateY(-1px);
}
.setting-card strong {
  display: block; font-family: var(--serif); font-size: 18px; font-weight: 560;
  letter-spacing: -.012em;
}
.setting-now { display: block; font-size: 14px; color: var(--ink); margin-top: 4px; font-weight: 550; }
.setting-sub {
  display: block; font-size: 13px; color: var(--ink-muted); margin-top: 6px; line-height: 1.5;
}
.setting-card .pill { margin-top: auto; }
.setting-card .setting-sub { margin-bottom: 14px; }

/* ---- payment providers

   Two rows of cards: what is connected, then what could be. The order is the
   point — somebody arriving here either has money coming in and wants to check
   it, or has none and wants to start, and both answers are in the first screen.

   Brand colour is the one exception to the rule that colour means good, bad or
   busy here. A logo means a COMPANY, and a Stripe purple that turned green
   would stop being Stripe's. It appears on the mark and nowhere else. */
.provider-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 18px;
}
.provider-card {
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 22px 24px 20px; box-shadow: var(--shadow);
}
/* One that wants something done is the only card that leans forward. */
.provider-card.todo { border-color: var(--warn); }
.provider-card.dim { opacity: .72; }

.provider-top { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; }
.provider-id { min-width: 0; flex: 1; }
.provider-id strong {
  display: block; font-family: var(--serif); font-size: 20px; font-weight: 560;
  letter-spacing: -.015em; line-height: 1.2;
}
.provider-sub {
  display: block; font-size: 12.5px; color: var(--ink-muted); margin-top: 3px;
  line-height: 1.45;
}
.provider-top .pill { flex: none; align-self: flex-start; }
.provider-pitch { font-size: 13.5px; color: var(--ink-2); line-height: 1.55; margin: 0 0 16px; }
.provider-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
.provider-foot {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin-top: auto; padding-top: 4px;
}
.provider-foot form { margin: 0; }

/* The two halves of a connection, said as a checklist rather than a status
   word: "pending" told nobody which half was missing. */
.conn-check { list-style: none; margin: 0 0 16px; padding: 0; display: grid; gap: 8px; }
.conn-check li {
  display: flex; align-items: flex-start; gap: 9px;
  font-size: 13px; color: var(--ink-2); line-height: 1.45;
}
.conn-check .tick {
  flex: none; width: 17px; height: 17px; border-radius: 50%;
  display: grid; place-items: center; margin-top: 1px;
}
.conn-check .tick svg { width: 11px; height: 11px; }
.conn-check li.done .tick { background: var(--good-sunk); color: var(--good-ink); }
.conn-check li.todo .tick { background: var(--warn-sunk); color: var(--warn-ink); }
.conn-check li.todo { color: var(--warn-ink); font-weight: 550; }

.section-note { font-size: 13.5px; color: var(--ink-muted); margin: -8px 0 14px; }
.hint.fine { font-size: 12.5px; color: var(--ink-muted); margin-top: 30px; max-width: 62ch; }

/* ---- a provider's own mark */
.plogo {
  flex: none; width: 44px; height: 44px; border-radius: 13px;
  display: grid; place-items: center;
}
.plogo svg { width: 26px; height: 26px; display: block; }
.plogo-stripe      { background: #ece9fe; color: #635bff; }
.plogo-paypal      { background: #e7eefa; }
.plogo-paypal .pp-back  { color: #003087; }
.plogo-paypal .pp-front { color: #009cde; }
.plogo-nowpayments { background: #e2f1fb; color: #0b8fd4; }

/* ---- connecting one, step by step */
.brand-head {
  display: flex; align-items: center; gap: 16px;
  padding-bottom: 20px; margin-bottom: 22px; border-bottom: 1px solid var(--line);
}
.brand-head h2 { margin: 0 0 4px; }
.brand-head .hint { margin: 0; }
.brand-head .pill { margin-left: auto; flex: none; }
.mini-head {
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-muted); font-weight: 650; margin: 26px 0 10px;
  font-family: var(--sans);
}
/* The one string somebody has to carry to another website, so it is set apart
   and set in mono: a URL read out of a paragraph gets a word dropped. */
.callback {
  background: var(--surface-sunk); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 12px 14px; margin: 0 0 6px;
  overflow-x: auto;
}
.callback code {
  font-family: var(--mono); font-size: 13px; color: var(--ink);
  white-space: nowrap; background: none; border: 0; padding: 0;
}
.event-list {
  list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px;
}
.event-list code {
  font-family: var(--mono); font-size: 12px; color: var(--ink-2);
  background: var(--surface-sunk); border: 1px solid var(--line);
  border-radius: 6px; padding: 3px 8px;
}
.button.sm { padding: 7px 14px; font-size: 13.5px; }

/* A brand wash has to sit on a dark card as well as a light one, and the marks
   themselves lighten so they stay legible against it. The hues do not move —
   these are still Stripe purple, PayPal blue and NOWPayments blue — only their
   weight against the ground behind them. Written twice, as everything themed
   here is: once for the switch, once for a machine set to dark. */
body.dark .plogo-stripe { background: #241f4d; color: #a79fff; }
body.dark .plogo-paypal { background: #16223a; }
body.dark .plogo-paypal .pp-back  { color: #7ba4e0; }
body.dark .plogo-paypal .pp-front { color: #4fc3f7; }
body.dark .plogo-nowpayments { background: #0d2837; color: #4fbdf0; }

@media (prefers-color-scheme: dark) {
  body:not(.light):not(.store):not(.site) .plogo-stripe { background: #241f4d; color: #a79fff; }
  body:not(.light):not(.store):not(.site) .plogo-paypal { background: #16223a; }
  body:not(.light):not(.store):not(.site) .plogo-paypal .pp-back  { color: #7ba4e0; }
  body:not(.light):not(.store):not(.site) .plogo-paypal .pp-front { color: #4fc3f7; }
  body:not(.light):not(.store):not(.site) .plogo-nowpayments { background: #0d2837; color: #4fbdf0; }
}

/* ---- the sign-in page

   Two halves: the card on the right, and on the left a drawn account of what
   this thing does — money arriving, people arriving, a door opening. It moves,
   because a still picture of a dashboard reads as a screenshot of somebody
   else's product, while a number ticking up reads as a thing that is running.

   Everything here is CSS and inline SVG. No script is allowed on this page at
   all, which is the point: it is the one page a stranger with no session can
   reach, so there is nothing on it that can execute. */
main.bare:has(.gate) { display: block; padding: 0; }
/* Both halves run the full height, so the width is stated rather than left to
   the grid: `main.bare` centres its child, and a grid item under `justify-items:
   center` is sized to its CONTENT — which left the panel floating in the middle
   of the window with a margin either side. */
.gate {
  display: grid; grid-template-columns: 1.05fr .95fr;
  width: 100%; min-height: 100vh;
}
.gate-form { display: grid; place-items: center; padding: 40px 32px; }

.gate-art {
  position: relative; overflow: hidden; align-self: stretch;
  display: flex; flex-direction: column; justify-content: center; gap: 34px;
  /* Centred in its own half rather than pinned to the left edge of it: the
     cards are narrower than the panel, and left-aligned they read as content
     that failed to fill the space rather than as a composition. */
  align-items: center;
  padding: 56px clamp(32px, 5vw, 60px);
  background: var(--gate-ground);
  border-right: 1px solid var(--line);
}
/* Two slow washes of colour behind the cards. They are what makes the panel
   feel lit rather than filled. */
.gate-art .glow {
  position: absolute; border-radius: 50%; filter: blur(70px); opacity: .55;
  pointer-events: none;
}
.gate-art .g1 {
  width: 460px; height: 460px; top: -120px; left: -110px;
  background: var(--gate-glow-a); animation: drift-a 22s ease-in-out infinite;
}
.gate-art .g2 {
  width: 380px; height: 380px; bottom: -110px; right: -60px;
  background: var(--gate-glow-b); animation: drift-b 26s ease-in-out infinite;
}
@keyframes drift-a {
  0%, 100% { transform: translate3d(0,0,0) scale(1); }
  50%      { transform: translate3d(40px, 50px, 0) scale(1.12); }
}
@keyframes drift-b {
  0%, 100% { transform: translate3d(0,0,0) scale(1.06); }
  50%      { transform: translate3d(-46px, -34px, 0) scale(.94); }
}

.art-stack { position: relative; display: grid; gap: 16px; max-width: 452px; }
.art-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 18px; box-shadow: var(--shadow-lift);
  padding: 20px 22px; opacity: 0;
  animation: rise-in .7s cubic-bezier(.2,.7,.3,1) forwards;
}
.art-chart  { animation-delay: .05s; }
.art-people { animation-delay: .35s; margin-left: 34px; }
.art-pay    { animation-delay: .65s; margin-left: 12px; }
@keyframes rise-in {
  from { opacity: 0; transform: translateY(16px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

/* ---- the chart card */
.art-card-top {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  column-gap: 10px; margin-bottom: 12px;
}
.art-label {
  grid-column: 1 / -1; font-size: 11.5px; text-transform: uppercase;
  letter-spacing: .09em; color: var(--ink-muted); font-weight: 650; margin-bottom: 4px;
}
.art-figure {
  font-family: var(--serif); font-size: 34px; font-weight: 560;
  letter-spacing: -.02em; color: var(--ink); font-variant-numeric: tabular-nums;
}
.art-delta {
  font-size: 13px; font-weight: 600; color: var(--good-ink);
  background: var(--good-sunk); border-radius: var(--radius-pill); padding: 2px 9px;
}
.art-plot { display: block; width: 100%; height: 96px; }
.art-area { fill: var(--accent-sunk); opacity: 0; animation: fade-area 1s ease .9s forwards; }
@keyframes fade-area { to { opacity: 1; } }
/* The line draws itself, then the marker at its end keeps a slow pulse so the
   card never settles into a screenshot. */
.art-line {
  fill: none; stroke: var(--accent); stroke-width: 2.6;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 420; stroke-dashoffset: 420;
  animation: draw 1.8s cubic-bezier(.35,.1,.25,1) .45s forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }
.art-dot {
  fill: var(--accent); opacity: 0;
  animation: pop-dot .4s ease 2.1s forwards, throb 2.6s ease-in-out 2.5s infinite;
}
@keyframes pop-dot { from { opacity: 0; r: 0; } to { opacity: 1; r: 4; } }
@keyframes throb {
  0%, 100% { r: 4; }
  50%      { r: 6; }
}
/* A week of takings under the line, each bar growing in turn. */
.art-bars {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 7px;
  align-items: end; height: 40px; margin-top: 14px;
}
.art-bars i {
  display: block; background: var(--accent); opacity: .28; border-radius: 4px;
  height: 0; transform-origin: bottom;
  animation: grow-bar .8s cubic-bezier(.2,.8,.3,1) forwards;
}
@keyframes grow-bar { to { height: var(--h); } }
.art-bars .b1 { --h: 42%; animation-delay: 1.0s; }
.art-bars .b2 { --h: 62%; animation-delay: 1.1s; }
.art-bars .b3 { --h: 36%; animation-delay: 1.2s; }
.art-bars .b4 { --h: 78%; animation-delay: 1.3s; }
.art-bars .b5 { --h: 55%; animation-delay: 1.4s; }
.art-bars .b6 { --h: 88%; animation-delay: 1.5s; }
.art-bars .b7 { --h: 100%; animation-delay: 1.6s; opacity: .85; }

/* ---- the subscribers card */
.art-people { display: flex; align-items: center; gap: 16px; }
.art-faces { display: flex; }
.art-faces .face {
  width: 38px; height: 38px; border-radius: 50%; flex: none;
  display: grid; place-items: center; font-size: 14px; font-weight: 640;
  border: 2.5px solid var(--surface); margin-left: -11px;
  opacity: 0; animation: pop-face .45s cubic-bezier(.2,.9,.3,1) forwards;
}
.art-faces .face:first-child { margin-left: 0; }
@keyframes pop-face {
  from { opacity: 0; transform: scale(.5); }
  to   { opacity: 1; transform: none; }
}
.art-faces .f1 { background: var(--t-blue-sunk);   color: var(--t-blue);   animation-delay: .8s; }
.art-faces .f2 { background: var(--t-violet-sunk); color: var(--t-violet); animation-delay: .95s; }
.art-faces .f3 { background: var(--t-amber-sunk);  color: var(--t-amber);  animation-delay: 1.1s; }
.art-faces .f4 { background: var(--t-teal-sunk);   color: var(--t-teal);   animation-delay: 1.25s; }
.art-faces .f5 { background: var(--accent-sunk);   color: var(--accent-ink); animation-delay: 1.4s; }
.art-people-text strong {
  display: block; font-family: var(--serif); font-size: 19px; font-weight: 560;
  letter-spacing: -.015em; font-variant-numeric: tabular-nums;
}
.art-people-text small { display: block; font-size: 12.5px; color: var(--ink-muted); margin-top: 2px; }

/* ---- the payment card */
.art-pay { display: flex; align-items: center; gap: 14px; }
.art-tick {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: var(--good-sunk); color: var(--good-ink);
  animation: ring 3.2s ease-out 1.7s infinite;
}
.art-tick svg { width: 17px; height: 17px; }
/* A ring that goes out from the tick every few seconds — one payment landing,
   then another. */
@keyframes ring {
  0%       { box-shadow: 0 0 0 0 var(--good-sunk); }
  40%, 100% { box-shadow: 0 0 0 14px transparent; }
}
.art-pay-text strong {
  display: block; font-size: 15px; font-weight: 600; letter-spacing: -.01em;
}
.art-pay-text small { display: block; font-size: 12.5px; color: var(--ink-muted); margin-top: 2px; }

.art-caption {
  position: relative; max-width: 452px; margin: 0;
  font-family: var(--serif); font-size: 23px; line-height: 1.45;
  letter-spacing: -.015em; color: var(--ink-2); text-wrap: balance;
  opacity: 0; animation: rise-in .8s ease .95s forwards;
}

/* Under about a laptop there is no room for two columns, and the card is the
   part somebody came for. */
@media (max-width: 900px) {
  .gate { grid-template-columns: 1fr; min-height: 0; }
  .gate-art { display: none; }
  main.bare:has(.gate) { display: grid; place-items: center; min-height: 100vh; }
}

/* Somebody who has asked their machine to stop moving things gets the finished
   frame, not a still of the first frame — every one of these animations ends
   somewhere visible, so the fix is to jump to the end rather than to leave the
   panel blank. */
@media (prefers-reduced-motion: reduce) {
  .gate-art .glow, .art-card, .art-area, .art-line, .art-dot,
  .art-bars i, .art-faces .face, .art-tick, .art-caption {
    animation: none !important;
  }
  .art-card, .art-caption, .art-area, .art-dot { opacity: 1; }
  .art-line { stroke-dashoffset: 0; }
  .art-bars i { height: var(--h); }
  .art-faces .face { opacity: 1; }
}

/* A row of actions at the foot of a panel or a card.

   These used to be link text with an arrow after it — "Change plan →" — which
   reads as a footnote rather than as the thing the panel wants you to do next.
   A control that performs the panel's action looks like a control. Link text is
   for going somewhere ELSE mid-sentence; a button is for doing the thing. */
.card-actions {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin: 18px 0 0;
}
.card-actions:first-child { margin-top: 0; }

/* ---- the tier a workspace is on

   Worn beside the workspace's name in the rail, because "which plan am I on"
   is a question asked from anywhere and answered nowhere until you go looking
   for the billing page. Quiet by default — it is a fact, not a warning — and
   amber only while a trial is running out or a payment has failed, which are
   the two states that actually want doing something about. */
.tier-badge {
  display: inline-block; margin-top: 4px;
  font-size: 10.5px; font-weight: 660; letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 8px; border-radius: var(--radius-pill);
  background: var(--accent-sunk); color: var(--accent-ink);
  white-space: nowrap; max-width: 148px; overflow: hidden; text-overflow: ellipsis;
}
.tier-badge.trial { background: var(--t-blue-sunk); color: var(--t-blue); }
.tier-badge.over  { background: var(--warn-sunk);   color: var(--warn-ink); }
/* The rail's block is a flex row of mark + text; the badge belongs in the text
   column, under the name rather than beside it, or a long workspace name and a
   long tier name fight over the same 148px. */
.brand .tier-badge { margin-top: 3px; }
.brand small { margin-top: 3px; }

/* ---- the storefront editor

   Controls on the left, the shop itself on the right. The preview is not a
   picture: it is the same words, the same plans and the same prices, arranged
   the way the chosen layout arranges them, so what somebody sees here is what a
   buyer sees at the address above it.

   Colour and background repaint WITHOUT a save, because the radio buttons and
   the preview are siblings under .editor and :has() can read which one is
   checked. Words cannot work that way — CSS cannot see what has been typed into
   a field — so they land on save. That split is stated on the page rather than
   left to be discovered. */
.editor {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: 32px; align-items: start;
}
.editor-controls { min-width: 0; }
.editor-controls .form { margin-bottom: 8px; }
/* The preview follows the page down: the colour controls are near the top and
   the words are near the bottom, and a preview that scrolled away would be
   answering a question nobody could still see. */
.editor-preview { position: sticky; top: 22px; min-width: 0; }
.preview-head { margin-bottom: 12px; }
.preview-head strong {
  display: block; font-size: 11.5px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-muted); font-weight: 650; margin-bottom: 4px;
}
.preview-head .hint { margin: 0; }

/* A window frame, so the preview reads as another page rather than as more of
   this one. */
.preview-frame {
  border: 1px solid var(--line-strong); border-radius: 14px; overflow: hidden;
  box-shadow: var(--shadow-lift); background: var(--surface);
}
.preview-chrome {
  display: flex; align-items: center; gap: 6px;
  padding: 9px 12px; background: var(--surface-sunk);
  border-bottom: 1px solid var(--line);
}
.preview-chrome .dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--line-strong); flex: none;
}
.preview-url {
  flex: 1; margin-left: 8px; padding: 3px 10px; border-radius: var(--radius-pill);
  background: var(--surface); border: 1px solid var(--line);
  font-family: var(--mono); font-size: 10.5px; color: var(--ink-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- the shop, in miniature

   Its own token set, prefixed --sp-*, so nothing here can reach the dashboard
   around it. The accent four come from /storefront/_preview.css, built from the
   saved hex by the same arithmetic the real shop uses; the neutrals below are
   the shop's own light palette, swapped wholesale for the dark one. */
.shop-preview {
  --sp-bg:      #faf9f7;
  --sp-card:    #ffffff;
  --sp-ink:     #14140f;
  --sp-ink-2:   #5c5b55;
  --sp-line:    #e6e5df;
  background: var(--sp-bg); color: var(--sp-ink);
  padding: 26px 24px 22px; font-size: 13px;
}
/* Dark is the saved state; the rule below it is the live one. */
.shop-preview.dark,
.editor:has(input[name="theme"][value="dark"]:checked) .shop-preview {
  --sp-bg:    #101010;
  --sp-card:  #191918;
  --sp-ink:   #f5f4f1;
  --sp-ink-2: #b3b1ab;
  --sp-line:  #2a2a28;
}
/* ...and picking light again has to win over a saved dark, which is why this
   pair exists rather than one rule per state. */
.editor:has(input[name="theme"][value="light"]:checked) .shop-preview {
  --sp-bg:    #faf9f7;
  --sp-card:  #ffffff;
  --sp-ink:   #14140f;
  --sp-ink-2: #5c5b55;
  --sp-line:  #e6e5df;
}

.sp-hero { text-align: center; padding: 6px 0 22px; }
.sp-name {
  display: inline-block; font-size: 11px; font-weight: 650; letter-spacing: .08em;
  text-transform: uppercase; color: var(--sp-accent-ink);
  background: var(--sp-accent-sunk); border-radius: var(--radius-pill); padding: 3px 11px;
}
.sp-logo { display: block; margin: 0 auto; max-height: 28px; width: auto; }
.sp-hero h3 {
  font-family: var(--serif); font-size: 24px; font-weight: 560; letter-spacing: -.02em;
  margin: 12px 0 7px; color: var(--sp-ink); text-transform: none; text-wrap: balance;
}
.sp-hero p {
  margin: 0 auto; max-width: 40ch; font-size: 12.5px; line-height: 1.55; color: var(--sp-ink-2);
}

.sp-plans { display: grid; gap: 12px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.sp-card {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  background: var(--sp-card); border: 1px solid var(--sp-line);
  border-radius: 12px; padding: 15px 16px;
}
.sp-top { display: flex; align-items: baseline; gap: 7px; }
.sp-emoji { font-size: 15px; }
.sp-card strong { font-size: 14px; font-weight: 620; letter-spacing: -.01em; color: var(--sp-ink); }
.sp-note {
  margin: 0; font-size: 11.5px; line-height: 1.45; color: var(--sp-ink-2);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.sp-price { margin: 2px 0 0; color: var(--sp-ink-2); font-size: 11.5px; }
.sp-price b {
  font-family: var(--serif); font-size: 19px; font-weight: 560; letter-spacing: -.015em;
  color: var(--sp-ink); margin-right: 4px;
}
/* Pushed to the bottom edge, so a row of cards has its buttons in a line
   however long the plan names above them run. */
.sp-btn {
  margin-top: auto; align-self: stretch; text-align: center;
  background: var(--sp-accent); color: var(--sp-on-accent);
  border-radius: 9px; padding: 8px 12px; font-size: 12px; font-weight: 620;
}
.sp-empty {
  margin: 0; padding: 22px; text-align: center; font-size: 12.5px; color: var(--sp-ink-2);
  background: var(--sp-card); border: 1px dashed var(--sp-line); border-radius: 12px;
}
.sp-foot {
  margin: 20px 0 0; text-align: center; font-size: 11px; color: var(--sp-ink-2); opacity: .8;
}

/* The three arrangements, live off the radio buttons. */
.editor:has(input[name="template"][value="list"]:checked) .sp-plans {
  grid-template-columns: minmax(0, 1fr);
}
.editor:has(input[name="template"][value="list"]:checked) .sp-card {
  flex-direction: row; align-items: center; gap: 14px;
}
.editor:has(input[name="template"][value="list"]:checked) .sp-note { display: none; }
.editor:has(input[name="template"][value="list"]:checked) .sp-price { margin-left: auto; }
.editor:has(input[name="template"][value="list"]:checked) .sp-btn {
  margin: 0; align-self: center; flex: none;
}
/* Spotlight pulls the first plan out, which in a preview means making it span
   the row and giving it the accent. */
.editor:has(input[name="template"][value="spotlight"]:checked) .sp-card:first-child {
  grid-column: 1 / -1; border-color: var(--sp-accent); padding: 20px 18px;
}
.editor:has(input[name="template"][value="spotlight"]:checked) .sp-card:first-child .sp-price b {
  font-size: 24px;
}

/* Under a laptop there is no room to put a preview beside a form. The controls
   come first, because that is what somebody came here to use. */
@media (max-width: 1100px) {
  .editor { grid-template-columns: minmax(0, 1fr); }
  .editor-preview { position: static; }
}

/* The eight presets, painted the instant one is chosen rather than on save.

   GENERATED. previewPresetRules() in gen_preview_css_test.go builds these from
   previewCSS — the same function that serves the saved colour — and a test
   fails if this block and that function drift apart. A swatch that paints one
   colour instantly and a different one after saving is worse than a swatch that
   does nothing at all.

   To regenerate after adding a preset:
     PREVIEW_CSS_OUT=/tmp/preset.css go test ./internal/web/ -run DumpPreviewCSS
*/
.editor:has(input[name="accent"][value="#0f9d63"]:checked) .shop-preview {
  --sp-accent: #0f9d63;
  --sp-accent-deep: #0c8051;
  --sp-on-accent: #141414;
  --sp-accent-sunk: #e7f5ef;
  --sp-accent-ink: #09613d;
}
.editor:has(input[name="accent"][value="#0f9d63"]:checked):has(input[name="theme"][value="dark"]:checked) .shop-preview {
  --sp-accent-sunk: #053521;
  --sp-accent-ink: #93d2b8;
}
.editor:has(input[name="accent"][value="#2a78d6"]:checked) .shop-preview {
  --sp-accent: #2a78d6;
  --sp-accent-deep: #2262af;
  --sp-on-accent: #ffffff;
  --sp-accent-sunk: #e9f1fa;
  --sp-accent-ink: #1a4a84;
}
.editor:has(input[name="accent"][value="#2a78d6"]:checked):has(input[name="theme"][value="dark"]:checked) .shop-preview {
  --sp-accent-sunk: #0e2848;
  --sp-accent-ink: #9fc2ec;
}
.editor:has(input[name="accent"][value="#6f4fd8"]:checked) .shop-preview {
  --sp-accent: #6f4fd8;
  --sp-accent-deep: #5b40b1;
  --sp-on-accent: #ffffff;
  --sp-accent-sunk: #f0edfb;
  --sp-accent-ink: #443085;
}
.editor:has(input[name="accent"][value="#6f4fd8"]:checked):has(input[name="theme"][value="dark"]:checked) .shop-preview {
  --sp-accent-sunk: #251a49;
  --sp-accent-ink: #beafed;
}
.editor:has(input[name="accent"][value="#d0453b"]:checked) .shop-preview {
  --sp-accent: #d0453b;
  --sp-accent-deep: #aa3830;
  --sp-on-accent: #ffffff;
  --sp-accent-sunk: #faeceb;
  --sp-accent-ink: #802a24;
}
.editor:has(input[name="accent"][value="#d0453b"]:checked):has(input[name="theme"][value="dark"]:checked) .shop-preview {
  --sp-accent-sunk: #461714;
  --sp-accent-ink: #e9aba6;
}
.editor:has(input[name="accent"][value="#e8912a"]:checked) .shop-preview {
  --sp-accent: #e8912a;
  --sp-accent-deep: #be7622;
  --sp-on-accent: #141414;
  --sp-accent-sunk: #fcf4e9;
  --sp-accent-ink: #8f591a;
}
.editor:has(input[name="accent"][value="#e8912a"]:checked):has(input[name="theme"][value="dark"]:checked) .shop-preview {
  --sp-accent-sunk: #4e310e;
  --sp-accent-ink: #f4cd9f;
}
.editor:has(input[name="accent"][value="#0e9aa7"]:checked) .shop-preview {
  --sp-accent: #0e9aa7;
  --sp-accent-deep: #0b7e88;
  --sp-on-accent: #141414;
  --sp-accent-sunk: #e6f4f6;
  --sp-accent-ink: #085f67;
}
.editor:has(input[name="accent"][value="#0e9aa7"]:checked):has(input[name="theme"][value="dark"]:checked) .shop-preview {
  --sp-accent-sunk: #043438;
  --sp-accent-ink: #92d1d7;
}
.editor:has(input[name="accent"][value="#1f2937"]:checked) .shop-preview {
  --sp-accent: #1f2937;
  --sp-accent-deep: #19212d;
  --sp-on-accent: #ffffff;
  --sp-accent-sunk: #e8e9eb;
  --sp-accent-ink: #131922;
}
.editor:has(input[name="accent"][value="#1f2937"]:checked):has(input[name="theme"][value="dark"]:checked) .shop-preview {
  --sp-accent-sunk: #0a0d12;
  --sp-accent-ink: #9a9ea5;
}
.editor:has(input[name="accent"][value="#c2185b"]:checked) .shop-preview {
  --sp-accent: #c2185b;
  --sp-accent-deep: #9f134a;
  --sp-on-accent: #ffffff;
  --sp-accent-sunk: #f8e7ee;
  --sp-accent-ink: #780e38;
}
.editor:has(input[name="accent"][value="#c2185b"]:checked):has(input[name="theme"][value="dark"]:checked) .shop-preview {
  --sp-accent-sunk: #41081e;
  --sp-accent-ink: #e397b5;
}


/* ---- the bot's conversation, drawn

   A left-to-right flow: one column per step, one card per thing that can
   happen. Branches fan out from a vertical bus at the left of each column and
   join each card with a stub, which is the shape a reader already knows from
   every other tree they have seen.

   The lines are CSS, not SVG, because a drawn line has to know where two boxes
   ended up and CSS pseudo-elements already do: the bus is a column-height rule
   at the stage's left edge, and each card reaches back to it. Nothing here
   needs measuring, so nothing here needs script — which matters, because this
   page allows none. */
.flow-canvas {
  overflow-x: auto; overflow-y: hidden;
  margin: 0 -4px 22px; padding: 4px 4px 14px;
  background:
    radial-gradient(circle at 1px 1px, var(--flow-dot) 1px, transparent 0) 0 0 / 22px 22px;
  border: 1px solid var(--line); border-radius: var(--radius);
}
.flow-track {
  display: flex; align-items: flex-start; gap: 0;
  padding: 24px 22px; width: max-content; min-width: 100%;
}
.flow-stage { position: relative; display: grid; gap: 14px; align-content: start; width: 268px; }
.flow-stage + .flow-stage { margin-left: 46px; }

/* The vertical bus, drawn only where there is more than one card to join. A
   single card gets a straight stub and nothing else. */
.flow-stage + .flow-stage:has(.node + .node)::before {
  content: ""; position: absolute; left: -24px; top: 46px; bottom: 46px;
  width: 2px; background: var(--flow-line); border-radius: 2px;
}
.flow-stage + .flow-stage .node::before {
  content: ""; position: absolute; left: -24px; top: 40px;
  width: 24px; height: 2px; background: var(--flow-line);
}
/* An arrowhead where the stub meets the card, so the direction is not left to
   be inferred from the reading order. */
.flow-stage + .flow-stage .node::after {
  content: ""; position: absolute; left: -4px; top: 36px;
  border: 5px solid transparent; border-left-color: var(--flow-line); border-right: 0;
}

.stage-label {
  display: flex; align-items: center; gap: 8px; margin: 0 0 2px;
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-muted); font-weight: 650;
}
.stage-n {
  display: grid; place-items: center; width: 19px; height: 19px; flex: none;
  border-radius: 50%; background: var(--surface-sunk); border: 1px solid var(--line-strong);
  font-size: 10.5px; letter-spacing: 0; color: var(--ink-2);
}

.node {
  position: relative;
  background: var(--surface); border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: 12px; padding: 15px 16px 14px; box-shadow: var(--shadow);
}
.node strong {
  display: block; font-size: 14.5px; font-weight: 620; letter-spacing: -.012em;
  color: var(--ink); margin-bottom: 5px;
}
.node p { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-2); }
.node-chip {
  display: grid; place-items: center; width: 28px; height: 28px;
  border-radius: 9px; margin-bottom: 10px;
  background: var(--surface-sunk); color: var(--ink-2);
}
.node-chip svg { width: 16px; height: 16px; }
.node-note {
  display: block; margin-top: 10px; font-size: 11.5px; font-weight: 560;
  color: var(--ink-muted); font-variant-numeric: tabular-nums;
}
.node-edit {
  display: inline-block; margin-top: 11px; font-size: 12px; font-weight: 600;
  padding: 5px 11px; border-radius: var(--radius-pill);
  background: var(--surface-sunk); border: 1px solid var(--line-strong);
  color: var(--ink); text-decoration: none;
}
.node-edit:hover { background: var(--plane); border-color: var(--ink-muted); }

/* What kind of thing each card is, said with the edge rather than with a label:
   five words repeated down a column is five words nobody reads twice. */
.node.n-trigger { border-left-color: var(--accent); }
.node.n-trigger .node-chip { background: var(--accent-sunk); color: var(--accent-ink); }
.node.n-route   { border-left-color: var(--t-blue); }
.node.n-route .node-chip { background: var(--t-blue-sunk); color: var(--t-blue); }
.node.n-says    { border-left-color: var(--t-violet); }
.node.n-says .node-chip { background: var(--t-violet-sunk); color: var(--t-violet); }
.node.n-does    { border-left-color: var(--t-teal); }
.node.n-does .node-chip { background: var(--t-teal-sunk); color: var(--t-teal); }
.node.n-stop    { border-left-color: var(--line-strong); }

/* Amber wins over the kind colour: a card that cannot happen yet is the one
   thing on this canvas worth interrupting the reading for. */
.node.waiting { border-color: var(--warn); border-left-color: var(--warn); }
.node.waiting .node-note { color: var(--warn-ink); font-weight: 620; }

.flow-key {
  display: flex; flex-wrap: wrap; gap: 6px 20px; align-items: center;
  font-size: 12px; color: var(--ink-muted); margin-bottom: 8px;
}
.flow-key span { display: inline-flex; align-items: center; gap: 7px; }
.flow-key i { width: 12px; height: 3px; border-radius: 2px; flex: none; }
.flow-key .k-trigger { background: var(--accent); }
.flow-key .k-route   { background: var(--t-blue); }
.flow-key .k-says    { background: var(--t-violet); }
.flow-key .k-does    { background: var(--t-teal); }
.flow-key .k-waiting { background: var(--warn); }

/* ---- four classes that were doing nothing

   Each was written on an element and never given a rule, so it styled nothing
   and nobody could tell. They are here now with the rule each was clearly
   meant to have; the test that found them fails on the next one. */

/* The enrolment panel is a sequence, so its steps are separated rather than
   run together. */
.panel.enrol { padding-bottom: 8px; }
.enrol-step + .enrol-step { margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--line); }

/* A dropdown standing in for the page's one primary action sits on the title's
   baseline, the way a button in .page-head does. */
.dropdown.head-action { flex: none; margin-top: 6px; }

/* The two amount fields swap under :has(); the wrapper is what keeps the one
   being hidden from leaving a gap behind it. */
.kind-fields { display: grid; }

/* A provider that could be connected, but is not: the same card, one step back,
   so the connected row above reads as the foreground. */
.provider-card.offer { background: var(--plane); box-shadow: none; }
.provider-card.offer .provider-pitch { color: var(--ink-2); }

/* ---- the customer console

   The workspace list is where an operator spends their time, so it is built to
   be worked from rather than read. The chips across the top are the questions
   somebody actually opens this page to ask, each with how many wear it — a
   queue, not a legend. */
.worry-chips {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px; margin-bottom: 22px;
}
.worry {
  display: block; padding: 14px 16px; border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid var(--line);
  text-decoration: none; color: var(--ink); box-shadow: var(--shadow);
  transition: border-color .15s ease, transform .15s ease;
}
.worry:hover { border-color: var(--line-strong); transform: translateY(-1px); }
.worry.on { border-color: var(--accent); background: var(--accent-sunk); }
/* Nothing to worry about is still worth showing: a zero that disappears makes
   the row jump about, and "0 behind on payment" is the good news. */
.worry.none { opacity: .55; }
.worry-n {
  display: block; font-family: var(--serif); font-size: 26px; font-weight: 560;
  letter-spacing: -.02em; line-height: 1.1; font-variant-numeric: tabular-nums;
}
.worry-label { display: block; font-size: 13px; font-weight: 600; margin-top: 3px; }
.worry-why { display: block; font-size: 11.5px; color: var(--ink-muted); margin-top: 2px; }

.worry-banner { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 22px; }
.worry.small {
  display: flex; align-items: baseline; gap: 8px; padding: 8px 13px;
  border-left: 3px solid var(--warn);
}
.worry.small strong { font-size: 13px; font-weight: 620; }
.worry.small span { font-size: 11.5px; color: var(--ink-muted); }

/* One dot per worry, so a row says what is wrong with it without a column each.
   Amber throughout: these are all "wants attention", and giving each its own
   colour would invent five severities that do not exist. */
.worry-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 4px; background: var(--warn);
}
.worry-dot.ok { background: var(--line-strong); }
.wd-past_due { background: var(--danger); }

.console-filters {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px;
}
.console-filters .search { flex: 1 1 240px; }
.pick { display: flex; flex-direction: column; gap: 4px; font-size: 12px; }
.pick > span {
  font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-muted); font-weight: 650;
}
.pick select { min-width: 130px; }
.result-count { font-size: 13px; color: var(--ink-muted); margin: 16px 0 10px; }
table.console td { vertical-align: top; }
table.console .tag { margin-top: 4px; }

.pager { display: flex; align-items: center; gap: 14px; margin-top: 16px; }
.pager-at { font-size: 12.5px; color: var(--ink-muted); }

/* ---- one customer

   Two columns: what they are on the left, what you can do about it on the
   right. The levers are on the right because they are answers, and reaching
   for one before reading the left-hand column is the wrong order. */
.admin-cols {
  display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 28px; align-items: start;
}
.admin-main, .admin-side { min-width: 0; }
.admin-side .section-head:first-child { margin-top: 34px; }
.card .small-n { font-size: 21px; }

.note-form { margin-bottom: 20px; }
.note-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.note-list li {
  background: var(--surface-sunk); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 12px 14px;
}
.note-list li.pinned { border-color: var(--warn); background: var(--warn-sunk); }
.note-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  font-size: 11.5px; color: var(--ink-muted); margin-bottom: 6px;
}
.note-by { font-weight: 620; color: var(--ink-2); }
.note-when { font-variant-numeric: tabular-nums; }
.note-head form { margin: 0 0 0 auto; }
.note-list p { margin: 0; font-size: 13.5px; line-height: 1.55; white-space: pre-wrap; }

.timeline { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.timeline li {
  display: grid; grid-template-columns: 92px minmax(0, 1fr) auto;
  gap: 12px; align-items: baseline;
  padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 12.5px;
}
.timeline li:last-child { border-bottom: 0; }
.tl-when { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.tl-what { font-family: var(--mono); font-size: 11.5px; color: var(--ink); }
.tl-who { color: var(--ink-muted); text-align: right; }

.tight-form { margin-bottom: 18px; }
.tight-form:last-child { margin-bottom: 0; }
.days-row { display: flex; align-items: center; gap: 8px; }
.days-row input { width: 84px; }
.days-row span { font-size: 13px; color: var(--ink-2); }

@media (max-width: 1100px) {
  .admin-cols { grid-template-columns: minmax(0, 1fr); }
}

/* The banner that says this installation is not talking to Telegram at all.
   Wider than the form it sits above on the wizard, because it is the reason
   filling that form in will not work. */
.stub-warning { text-align: left; margin-bottom: 20px; }
.stub-warning code { font-family: var(--mono); font-size: .92em; }

/* The label on a field inside a wizard panel keeps its own line. Without this
   a long label and the rule beside it end up sharing a row with the control. */
.wizard .field label { display: block; }
