/* Design tokens for the console redesign.
 *
 * Every value here is transcribed from lab/design_handoff_asservato_console/README.md
 * ("Design tokens": Colour, Typography, Spacing/radius/shadow). This file is the
 * only place in the repository allowed to hold a colour or a font size —
 * scripts/check-design-drift.sh enforces that. If a component needs a new
 * colour, the design handoff gets a new token first.
 *
 * Where the README lists two hexes for one role (good/text is "#2f6b4a · #2c5c3f")
 * the first is the badge/text value and the second the stronger data value; they
 * are split into <role>-text and <role>-strong below.
 *
 * This file is the shared design contract between the two UI worlds: the
 * server-rendered redesign (public/screens.css, loaded after this file) and
 * the Solid app under web/src (which loads only this file). A class lives
 * here only while both worlds render it; a fleet slice moves a class here
 * when the Solid screen adopts it; when the last server page using a class
 * dies, the class moves out of screens.css with it. New component CSS for a
 * server-rendered page goes in screens.css, never here; new Solid-only CSS
 * goes in web/src and may use only the tokens declared below.
 */

/* The rail and the drill panel are flush with the window (§3/3b): full height,
 * touching the left, right, top and bottom edges, no gutter and no rounded
 * outer corners. A browser's default 8px body margin is exactly such a gutter —
 * it floated the navy rail off three edges and turned the panel's left seam into
 * a shadow with daylight behind it. So the shell zeroes it here rather than
 * leaving the flush rule to be undone by a user-agent stylesheet. */
.ds {
  margin: 0;
  font-family: var(--font-ui);
  color: var(--text-body);
  background: var(--bg-app);
}

/* Descendant rules of shared containers — the Solid pages render these too. */
.ds-copy-glyph svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ds-queue-row > * {
  height: 100%;
  box-sizing: border-box;
}

/* JetBrains Mono 400/500, self-hosted. The handoff pulls it from Google Fonts;
 * the console must not make a third-party request at runtime, so the woff2
 * files ship in public/fonts and are served from the same origin. */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/jetbrains-mono-400.woff2") format("woff2");
}

@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/jetbrains-mono-500.woff2") format("woff2");
}

:root {
  /* ---- Colour: surfaces and borders ---- */
  --bg-app: #f4f2f0;              /* application background behind cards */
  --bg-surface: #ffffff;          /* cards, headers, rows */
  --bg-subtle: #fcfbfa;           /* table header rows, card footers, filter and preset rails */
  --bg-row-hover: #fdfcfb;        /* row hover */
  --border-hairline: #f0eeeb;     /* internal dividers, metric separators */
  --border-row-divider: #f6f4f2;  /* divider between matrix rows */
  --border-row: #e9e6e2;          /* list row border */
  --border-card: #e5e2de;         /* card border */
  --border-strong: #d9d5d0;       /* buttons, inputs */

  /* ---- Colour: text ---- */
  --text-primary: #161514;        /* headings, names */
  --text-body: #26241f;           /* row body text */
  --text-secondary: #6f6b66;      /* supporting text */
  --text-muted: #9a948d;          /* captions, metadata */
  --text-faint: #b8b2ab;          /* machine ids, disabled values */

  /* ---- Colour: navy ---- */
  --navy-900: #10203f;            /* rail background, primary button, active toggle cell */
  --navy-800: #1c3157;            /* primary button hover, active rail item */
  --navy-700: #152a4e;            /* rail flyout background */
  --navy-600: #20335c;            /* data bars, primary series */
  --navy-400: #7f8daa;            /* data bars, secondary series (Stuttgart) */
  --navy-300: #9aa5bd;            /* canvas-derived, weekend/second muted series */
  --navy-200: #c9cedb;            /* data bars, tertiary / range track */
  --rail-icon: #8fa2c2;           /* inactive rail icons */
  --accent-link: #2c4a7c;         /* links */

  /* ---- Colour: selection ---- */
  --select-bg: #eef1f6;           /* selected row, selection bar, active preset, filter chip */
  --select-border-chip: #ccd6e6;  /* chip border */
  --select-border-row: #b9c4d8;   /* selected row border */

  /* ---- Colour: semantics. Colour is reserved for numbers that need a human;
   * zero problems renders grey or is absent. Green is always the good
   * direction, whatever the metric. ---- */
  --good-text: #2f6b4a;           /* positive badge text */
  --good-strong: #2c5c3f;         /* positive delta and bars */
  --good-bg: #ecf5ef;
  --good-border: #d3e7da;
  --warn-text: #7a5a2a;           /* warning badge text */
  --warn-strong: #b5651d;         /* warning number */
  --warn-bg: #fdf5e8;
  --warn-border: #f0e2c8;
  --warn-bar: #c08a3e;            /* amber data bars */
  --warn-bar-alt: #c07a4a;        /* second amber bar (funnel stages) */
  --bad-text: #8a3a3a;            /* danger badge text */
  --bad-strong: #b4534f;          /* danger number and bars */
  --bad-bg: #fbeceb;
  --bad-border: #f3d4d1;
  --neutral-badge-text: #5b5751;
  --neutral-badge-bg: #f2f0ed;
  --neutral-badge-border: #e3dfda;
  --alert-dot: #c46a6a;           /* alert dot on a rail icon */

  /* Canvas-derived: five values the token table never named, all of them on
     screens 2b–2d. They are here rather than inline for the same reason as
     everything above — a colour has one home. */
  --select-text: #42588a;         /* canvas-derived, secondary text on select/bg */
  --tan-bg: #f7f9fc;              /* canvas-derived, the service TAN panel */
  --tan-meta: #6f7f9c;            /* canvas-derived, where the TAN came from */
  --good-dot: #4c9a6d;            /* canvas-derived, the KYC state dot */
  --good-meta: #4d6b57;           /* canvas-derived, provenance under a good state */

  /* Five more the table never named, all of them on the billing screens
   * (3a–3d). Money that is open but not yet late is sand rather than red;
   * a funnel needs two steps between amber and grey; and the "next step"
   * block is the warning surface one shade warmer. */
  --series-open: #dbc9a6;         /* canvas-derived, still-open money */
  --navy-500: #3f5480;            /* canvas-derived, the second navy data bar */
  --stage-final: #8a6a3a;         /* canvas-derived, the final-notice stage */
  --neutral-bar: #8a7f74;         /* canvas-derived, a reason with no direction */
  --next-step-bg: #fffdf8;        /* canvas-derived, the next-step block */
  --panel-seam: #d5d0ca;          /* §3b, the one edge a flush panel draws */
  --tooltip-bg: #1c1b1a;          /* §12b, the copy-chip tooltip */
  --tooltip-text: #f4f2f0;
  --shadow-tooltip: 0 4px 12px rgba(28, 27, 26, 0.22);

  /* Three values the README names in prose rather than in the colour table
   * (§12 Charts, §15 Toggle). They are colours, so they belong here. */
  --gridline: #f2f0ed;            /* chart gridlines */
  --gridline-zero: #dcd8d3;       /* the 0-line, darker than the rest */
  --toggle-off: #cfcac4;          /* toggle track when the setting is off */

  /* Focus, per §14 Form field. */
  --pending-fill: #6d7a92;        /* design language §07: a pending button's navy drops to this */
  --focus-border: #8fa3c6;
  --focus-ring: 0 0 0 3px rgba(143, 163, 198, 0.22);

  /* ---- Typography ---- */
  --font-ui: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ---- Spacing ---- */
  --space-page: 20px 30px;        /* page padding */
  --space-card-header: 15px 20px; /* card header padding */
  --space-row-list: 10px;         /* padding around a list of rows */
  --space-row: 12px 14px;         /* row padding */
  --gap-rows: 6px;                /* between rows */
  --gap-cards: 20px;              /* between cards */
  --gap-columns: 16px;            /* between grid columns */
  --gap-fields: 15px;             /* between form fields (14–16px) */

  /* The row's own inset: 1px border + 14px padding. A header grid above the
   * rows must carry this on top of the container padding or every column sits
   * 15px off (§6 header alignment gotcha). */
  --row-inset: 15px;

  /* ---- Radius ---- */
  --r-shell: 10px;                /* app shell */
  --r-card: 9px;                  /* card */
  --r-row: 7px;                   /* row and inner block */
  --r-button: 6px;                /* button and chip */
  --r-control: 5px;               /* small control */
  --r-badge: 3px;                 /* badge */

  /* ---- Shadow ---- */
  --shadow-card: 0 2px 8px rgba(28, 27, 26, 0.06);
  --shadow-popover: 0 8px 24px rgba(28, 27, 26, 0.14);
  --shadow-panel: -18px 0 44px rgba(16, 32, 63, 0.20);
  --shadow-modal: 0 24px 60px rgba(16, 32, 63, 0.28);
  --shadow-flyout: 0 10px 30px rgba(8, 17, 35, 0.4);
  --backdrop: rgba(16, 32, 63, 0.30);
}

/* ---------------------------------------------------------------- *
 * Type roles. One class per row of the README's typography table.
 * ---------------------------------------------------------------- */

/* Mono is not decoration: every number, id, reference, date-time and delta
 * uses it, always tabular so digits line up down a column. */
.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.t-page-title {
  font-size: 24px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--text-primary);
}

.t-page-subtitle {
  font-size: 15px;
  font-weight: 400;
  color: var(--text-secondary);
}

.t-group-header {
  font-size: 19px;
  font-weight: 600;
  color: var(--text-primary);
}

.t-metric {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 23px;
  font-weight: 500;
  color: var(--text-primary);
}

.t-metric-big {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 30px;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}

.t-movement-today {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 22px;
  font-weight: 500;
  color: var(--text-primary);
}

.t-tan {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 28px;
  font-weight: 500;
  letter-spacing: 0.16em;
  color: var(--text-primary);
}

.t-section-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.t-card-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
}

.t-block-title {
  font-size: 14.5px;
  font-weight: 600;
  color: var(--text-primary);
}

.t-row-primary {
  font-size: 13.5px;
  font-weight: 400;
  color: var(--text-body);
}

.t-row-secondary {
  font-size: 12.5px;
  font-weight: 400;
  color: var(--text-muted);
}

.t-field-label {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.t-badge {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.t-button {
  font-size: 13px;
  font-weight: 400;
}

.t-button-primary {
  font-size: 13px;
  font-weight: 600;
}

.t-column-header {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ================================================================ *
 * The shell frame: the rail and the page beside it. Moved here from
 * screens.css verbatim when the Solid app under web/src started
 * rendering the same rail — both worlds draw it now, so it belongs to
 * the shared contract. Rules and comments are unchanged.
 * ================================================================ */
/* --- §1 Navigation rail: 60px, collapsed by default --- */
.ds-rail {
  position: relative;
  width: 60px;
  flex: none;
  background: var(--navy-900);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 14px 0;
  gap: 6px;
  z-index: 3;
}

.ds-rail-wordmark {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--bg-surface);
  padding-bottom: 12px;
}

/* The nav wrapper spans the whole 60px rail, not just the 38px icon. That gap
 * on either side of the icon used to be outside the hover target, so a pointer
 * moving right from the icon left the wrapper at x=49 and only reached the
 * flyout at x=60 — eleven pixels of dead zone that closed the menu before the
 * hand got there. */
.ds-rail-nav {
  position: relative;
  display: flex;
  width: 100%;
  justify-content: center;
}

.ds-rail-item {
  position: relative;
  width: 38px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  padding: 0;
  border-radius: var(--r-row);
  color: var(--rail-icon);
  background: none;
  cursor: pointer;
  text-decoration: none;
}

.ds-rail-item svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ds-rail-item.is-active {
  background: var(--navy-800);
  color: var(--bg-surface);
}

/* An alert dot rides on a section icon that holds work. */
.ds-rail-item.has-alert::after {
  content: "";
  position: absolute;
  top: 5px;
  right: 5px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--alert-dot);
}

.ds-rail-divider {
  width: 26px;
  height: 1px;
  background: var(--navy-800);
  margin: 10px 0;
}

.ds-rail-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--navy-800);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  color: var(--navy-200);
  text-decoration: none;
}

.ds-rail-spacer {
  flex: 1;
}

/* The flyout is the only sub-navigation there is: no horizontal tab strip,
 * because these are separate routes rather than panels of one page.
 *
 * It is hidden by visibility rather than display so it can carry an intent
 * delay: leaving the icon keeps the menu on screen for 150ms, which is the
 * grace a hand needs to travel into it. display:none had no such grace — the
 * menu vanished the instant the pointer crossed the icon's edge. */
.ds-rail-flyout {
  position: absolute;
  left: 60px;
  top: 0;
  width: 212px;
  background: var(--navy-700);
  border: 1px solid var(--navy-800);
  border-radius: 8px;
  box-shadow: var(--shadow-flyout);
  padding: 5px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  z-index: 4;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease 150ms, visibility 0s linear 270ms;
}

/* The bridge: an invisible strip under the flyout's left edge so a pointer
 * cutting the corner diagonally never crosses bare page between the icon and
 * the menu. It only exists while the flyout does. */
.ds-rail-flyout::before {
  content: "";
  position: absolute;
  left: -16px;
  top: 0;
  bottom: 0;
  width: 16px;
}

.ds-rail-nav:hover .ds-rail-flyout,
.ds-rail-nav:focus-within .ds-rail-flyout {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
  transition-delay: 0s, 0s;
}

.ds-rail-flyout-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--rail-icon);
  padding: 7px 11px 5px;
}

.ds-rail-flyout a {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 11px;
  border-radius: var(--r-control);
  font-size: 13px;
  color: var(--navy-200);
  text-decoration: none;
}

.ds-rail-flyout a:hover {
  background: var(--navy-800);
  color: var(--bg-surface);
}

.ds-rail-flyout .ds-count {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  color: var(--rail-icon);
}

/* --- Shell: rail beside one scrolling page --- */
.ds-app {
  display: flex;
  min-height: 100vh;
  align-items: stretch;
}

/* The rail stays put while the page scrolls — it is the spatial anchor the
 * handoff keeps collapsed for exactly that reason. */
.ds-app .ds-rail {
  position: sticky;
  top: 0;
  height: 100vh;
  border-radius: 0;
}

/* Pin-to-expand (0b). Shown because a user may pin it, not because it is
 * recommended: at 232px with a drill panel open the page is a 92px sliver. */
.ds-rail.is-expanded {
  width: 232px;
  align-items: stretch;
  padding: 14px 10px;
}

.ds-rail.is-expanded .ds-rail-wordmark {
  font-size: 12px;
  letter-spacing: 0.1em;
  padding: 0 10px 12px;
}

.ds-rail.is-expanded .ds-rail-item {
  width: auto;
  justify-content: flex-start;
  gap: 12px;
  padding: 0 11px;
}

.ds-rail.is-expanded .ds-rail-nav {
  display: block;
}

.ds-rail-label {
  font-size: 13px;
}

.ds-rail.is-expanded .ds-rail-avatar,
.ds-rail.is-expanded .ds-rail-divider {
  margin: 0 auto;
}

/* The rail's pin is a form so expanding POSTs a preference; the form itself
   must not affect layout. */
.ds-rail-form { display: contents; }

/* --- Cards: the frame most components sit in --- */
.ds-card {
  background: var(--bg-surface);
  border: 1px solid var(--border-card);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
}

.ds-card-head {
  padding: var(--space-card-header);
  border-bottom: 1px solid var(--border-hairline);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gap-columns);
}

.ds-card-foot {
  padding: 11px 20px;
  background: var(--bg-subtle);
  border-top: 1px solid var(--border-hairline);
  font-size: 12.5px;
  color: var(--text-muted);
}

/* --- Buttons: header actions, bulk actions, filter controls --- */
.ds-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  font-family: var(--font-ui);
  color: var(--text-body);
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-button);
  cursor: pointer;
  text-decoration: none;
}

.ds-btn:hover {
  background: var(--neutral-badge-bg);
}

.ds-btn-primary {
  color: var(--bg-surface);
  background: var(--navy-900);
  border-color: var(--navy-900);
  padding: 7px 15px;
}

.ds-btn-primary:hover {
  background: var(--navy-800);
  border-color: var(--navy-800);
}

/* --- Busy feedback: the console's one loading affordance -------------------
 * The handoff never drew a loading state, so this is the definition: a 14px
 * ring in the button border's own grey, turning once every 600ms. No colour —
 * colour is reserved for numbers that need a human, and "wait a moment" is not
 * one of them.
 *
 * Three carriers, one look: a form's submit button while its POST is on the
 * wire, a link that downloads instead of navigating, and a region that already
 * says aria-busy while it swaps its contents.
 */
.ds-busy-spinner {
  display: inline-block;
  flex: none;
  width: 14px;
  height: 14px;
  border: 1.5px solid var(--border-strong);
  border-top-color: var(--text-muted);
  border-radius: 50%;
  animation: ds-busy-spin 600ms linear infinite;
}

.ds-busy-spinner[hidden] {
  display: none;
}

@keyframes ds-busy-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes ds-busy-pulse {
  50% {
    opacity: 0.25;
  }
}

/* Reduced motion: the ring stops turning and breathes instead — the same rule
 * that turns the drill panel's slide into a fade. */
@media (prefers-reduced-motion: reduce) {
  .ds-busy-spinner {
    animation: ds-busy-pulse 1200ms ease-in-out infinite;
  }
}

/* A control that is already working takes no second click. Pending per the
 * design language §06/07 (ruling Tino 20.08., supersedes the ring-only form
 * of 19.08.): the label STAYS — never swap it, never let the width jump — the
 * spinner rides the leading edge, the fill drops to the muted pending tone
 * and the cursor says not-allowed. The state ends by itself. */
.ds-btn.is-busy,
.ds-menu-item.is-busy,
.ds-signin-form button[type="submit"].is-busy {
  cursor: not-allowed;
  pointer-events: none;
}

.ds-btn.is-busy > [data-busy-spinner],
.ds-signin-form button[type="submit"].is-busy > [data-busy-spinner] {
  order: -1;
}

.ds-btn-primary.is-busy,
.ds-signin-form button[type="submit"].is-busy {
  background: var(--pending-fill);
  border-color: var(--pending-fill);
}

/* A menu item stacks its label over its note, so the ring cannot ride at the
 * end of that flow — it sits at the item's right edge instead. */
.ds-menu-item.is-busy {
  position: relative;
}

.ds-menu-item.is-busy > [data-busy-spinner] {
  position: absolute;
  top: 50%;
  right: 10px;
  margin-top: -7px;
}

/* On the navy primary button and the sign-in submit the grey ring disappears,
 * so it borrows the button's own text colour there. Same ring, one animation. */
.ds-btn-primary.is-busy > [data-busy-spinner],
.ds-signin-form button[type="submit"].is-busy > [data-busy-spinner] {
  border-color: var(--navy-400);
  border-top-color: var(--bg-surface);
}

/* A region loading its own contents — the dunning case pane, the search
 * results. What it still shows is dimmed, so nothing on screen reads as current
 * while it is not, and one ring says why. */
[aria-busy="true"] {
  position: relative;
  opacity: 0.55;
}

[aria-busy="true"]::after {
  content: "";
  position: absolute;
  top: 18px;
  left: 50%;
  width: 14px;
  height: 14px;
  margin-left: -7px;
  border: 1.5px solid var(--border-strong);
  border-top-color: var(--text-muted);
  border-radius: 50%;
  animation: ds-busy-spin 600ms linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  [aria-busy="true"]::after {
    animation: ds-busy-pulse 1200ms ease-in-out infinite;
  }
}

/* --- §4 Page header --- */
.ds-page-header {
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border-card);
  padding: 20px 30px 0;
}

.ds-title-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 32px;
  padding-bottom: 14px;
}

.ds-actions {
  display: flex;
  align-items: center;
  gap: 9px;
  flex: none;
}

/* A clickable metric carries a small "›" after its label — and it is only
 * clickable when every metric in the strip resolves to a list. */
.ds-metric-link .t-section-label::after {
  content: " \203A";
  color: var(--text-faint);
}

/* --- §6 List row --- */

/* Row and header share one grid so the columns cannot drift apart. */
.ds-row-list {
  padding: 10px 20px 0;
  display: flex;
  flex-direction: column;
  gap: var(--gap-rows);
}

/* A list that ends the card needs its own bottom edge; one that is followed by
 * a footer borrows the footer's. */
.ds-card > .ds-row-list:last-child {
  padding-bottom: 10px;
}

.ds-row {
  display: grid;
  grid-template-columns: var(--cols, minmax(0, 1fr) 140px 140px 100px 34px);
  align-items: center;
  gap: var(--gap-columns);
  padding: var(--space-row);
  background: var(--bg-surface);
  border: 1px solid var(--border-row);
  border-radius: var(--r-row);
  text-decoration: none;
  color: inherit;
}

.ds-row:hover {
  background: var(--bg-row-hover);
}

.ds-row.is-selected {
  background: var(--select-bg);
  border-color: var(--select-border-row);
}

/* A cell: a value, optionally a second line under it, optionally a tag before
 * it. Right-aligned columns align their second line too, or the pair reads as
 * two different columns. */
.ds-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ds-cell.is-right {
  align-items: flex-end;
  text-align: right;
}

.ds-cell .t-row-secondary {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A value that is not there — the em dash where a lead has no locker. */
.ds-cell.is-faint .t-row-primary {
  color: var(--text-faint);
}

.t-column-header.is-right {
  text-align: right;
}

/* --- §7 Row overflow menu: 250px, state-aware --- */

/* The parent card must not clip it. */
.ds-row-list,
.ds-card.ds-has-menus {
  overflow: visible;
}

.ds-row-menu {
  position: absolute;
  top: 54px;
  right: 12px;
  width: 250px;
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-row);
  box-shadow: var(--shadow-popover);
  padding: 5px;
  display: flex;
  flex-direction: column;
  z-index: 3;
}

.ds-row-menu[hidden] {
  display: none;
}

.ds-menu-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--r-control);
  background: none;
  font-family: var(--font-ui);
  font-size: 13.5px;
  color: var(--text-body);
  text-decoration: none;
  cursor: pointer;
}

.ds-menu-item:hover {
  background: var(--bg-app);
}

.ds-menu-item:first-child {
  font-weight: 600;
  color: var(--navy-900);
}

.ds-menu-item:first-child:hover {
  background: var(--select-bg);
}

.ds-menu-note {
  font-size: 12.5px;
  color: var(--text-secondary);
}

.ds-menu-item.is-danger {
  font-weight: 600;
  color: var(--bad-text);
}

.ds-menu-item.is-danger:hover {
  background: var(--bad-bg);
}

/* Unavailable actions are greyed WITH the reason, never hidden — and the grey
 * is a colour on both lines, not an opacity on the block. */
.ds-menu-item.is-disabled,
.ds-menu-item.is-disabled .ds-menu-note {
  color: var(--text-faint);
  font-weight: 400;
  cursor: default;
}

.ds-menu-item.is-disabled:hover {
  background: none;
}

/* Inside this bar the secondary button borrows the selection border. */
.ds-selection-actions .ds-btn {
  border-color: var(--select-border-row);
}

.ds-selection-actions .ds-btn:hover {
  background: var(--bg-surface);
}

.ds-selection-actions .ds-btn-primary {
  border-color: var(--navy-900);
}

/* --- §13 Badges --- */
.ds-badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 7px;
  border-radius: var(--r-badge);
  border: 1px solid transparent;
}

.ds-badge-good {
  color: var(--good-text);
  background: var(--good-bg);
  border-color: var(--good-border);
}

.ds-badge-warn {
  color: var(--warn-text);
  background: var(--warn-bg);
  border-color: var(--warn-border);
}

.ds-badge-bad {
  color: var(--bad-text);
  background: var(--bad-bg);
  border-color: var(--bad-border);
}

.ds-badge-neutral {
  color: var(--neutral-badge-text);
  background: var(--neutral-badge-bg);
  border-color: var(--neutral-badge-border);
}

/* .mono is declared long before .ds-input, so .ds-input's own font-family won
   and every birthdate, postcode and VAT id in the form came out proportional
   while the value beside it in the read-only panel was tabular. Same weight of
   selector, later rule — so the mono case has to be stated here. */
.ds-input.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.ds-main {
  flex: 1;
  min-width: 0;
}

/* --- Page body: the cards under a header --- */
.ds-page-body {
  padding: 22px 30px 30px;
  display: flex;
  flex-direction: column;
  gap: var(--gap-cards);
}

.ds-queue-row > .ds-card {
  display: flex;
  flex-direction: column;
}

/* The body is whatever sits between the head and the foot: a list of rows, a
 * chart plot, a block of prose. Whichever it is, it absorbs the slack. */
.ds-queue-row > .ds-card > .ds-row-list,
.ds-queue-row > .ds-card > .ds-card-body {
  flex: 1;
  align-content: flex-start;
}

.ds-card-title-line {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}

/* --- Page header additions --- */
.ds-title-block {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

.ds-value-good { color: var(--good-strong); }

.ds-value-warn { color: var(--warn-strong); }

.ds-value-bad { color: var(--bad-strong); }

.ds-value-flat { color: var(--text-primary); }

/* --- Tonight's run --- */
.ds-row.is-job {
  grid-template-columns: minmax(0, 1fr) auto;
}

.ds-row.is-warn {
  border-color: var(--warn-border);
}

.ds-tonight .ds-row-list {
  flex: 1;
}

/* --- Event row (the feed line) --- */
.ds-row.is-event {
  grid-template-columns: 74px 180px minmax(0, 1fr) 128px;
  padding: 11px 14px;
}

.ds-row.is-event.has-menu {
  padding-right: 48px;
}

.ds-row.is-event.is-bad {
  border-color: var(--bad-border);
}

.ds-list-empty {
  margin: 0;
  padding: 40px 20px;
  text-align: center;
  font-size: 13.5px;
  color: var(--text-muted);
}

/* --- §12b Copyable id: the chip support pastes from --- */

/* One bordered container holding the id and the glyph, and the whole thing is
 * the hit target. A bare id invites a triple-click that takes the neighbouring
 * punctuation with it; this takes exactly the id. */
.ds-copy-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 2px 4px 2px 8px;
  font: inherit;
  background: var(--bg-subtle);
  border: 1px solid var(--neutral-badge-border);
  border-radius: var(--r-control);
  color: inherit;
  cursor: pointer;
}

.ds-copy-chip:hover {
  background: var(--neutral-badge-bg);
  border-color: var(--panel-seam);
}

.ds-copy-chip:focus-visible {
  outline: none;
  border-color: var(--focus-border);
  box-shadow: var(--focus-ring);
}

.ds-copy-value {
  font-size: 13px;
  color: var(--text-body);
}

/* The chip inside a list row. It borrows the row's own type size and never
 * outgrows its column: the value truncates, and because the chip copies
 * exactly what it shows, a truncated value is a signal that the column is too
 * narrow — not a chip that quietly copies more than the eye read. */
.ds-copy-chip.is-inline {
  max-width: 100%;
  padding: 1px 2px 1px 7px;
  gap: 4px;
}

.ds-copy-chip.is-inline .ds-copy-value {
  font-size: 12.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ds-copy-chip.is-inline .ds-copy-glyph {
  width: 18px;
  height: 18px;
}

.ds-copy-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 20px;
  border-radius: 4px;
  color: var(--text-secondary);
}

/* The tooltip lives on the chip, with its arrow beneath, so the confirmation
 * appears exactly where the click landed. */
.ds-copy-tip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 7px);
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  white-space: nowrap;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--tooltip-text);
  background: var(--tooltip-bg);
  border-radius: 5px;
  padding: 5px 9px;
  box-shadow: var(--shadow-tooltip);
  opacity: 0;
  pointer-events: none;
  transition: opacity 90ms ease;
}

.ds-copy-tip::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 100%;
  transform: translateX(-50%);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4px solid var(--tooltip-bg);
}

.ds-copy-chip:hover .ds-copy-tip,
.ds-copy-chip:focus-visible .ds-copy-tip,
.ds-copy-chip.is-copied .ds-copy-tip {
  opacity: 1;
}

/* The scratch field the execCommand fallback selects. It must be in the
 * document to be selectable and must never be seen. */
.ds-copy-sink {
  position: fixed;
  top: -1000px;
  left: -1000px;
  opacity: 0;
}

.ds-invoice-eyebrow {
  font-size: 12px;
  color: var(--text-muted);
}

/* One attempt: when, what, and how it ended. */
.ds-row.is-attempt {
  grid-template-columns: 96px minmax(0, 1fr) 110px;
  gap: 12px;
}

.ds-pagination-controls .ds-btn {
  flex: none;
  white-space: nowrap;
}

/* A dead control keeps its place: the buttons must not move as you page. */
.ds-btn.is-dead {
  color: var(--text-faint);
  border-color: var(--border-hairline);
  cursor: default;
}

.ds-btn.is-dead:hover {
  background: var(--bg-surface);
}

/* A note is a sentence: it wraps instead of being cut off. */
.ds-cell.is-wrap .t-row-primary {
  white-space: normal;
  overflow: visible;
  text-wrap: pretty;
}

.ds-cell.is-wrap {
  gap: 4px;
}

/* A row holding a wrapping sentence aligns to the top, not the middle: the
 * pinned row's note runs to two or three lines while the state beside it runs
 * to two, and centring both leaves the badge floating opposite the note's
 * second line. */
.ds-row:has(.ds-cell.is-wrap) {
  align-items: start;
}

.ds-identity-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

/* --- The header's own action menu (Charge ▾, ···) --- */
.ds-action-host {
  position: relative;
  /* Above the sticky jumper and the cards, below the overlay layer. */
  z-index: 46;
}

.ds-menu-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.ds-row-identity-line .t-row-primary {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* On the customer's own page the event row drops the name track: you are
 * already on the person, so a column repeating their name says nothing. */
.ds-row.is-event.is-nameless {
  grid-template-columns: 110px minmax(0, 1fr) 120px;
}

/* A jump lands the section under the sticky bar, not behind it. */
.ds-card[id] {
  scroll-margin-top: 56px;
}

.ds-meter-foot.ds-value-flat {
  color: var(--text-muted);
}

.ds-tile-note.ds-value-flat {
  color: var(--text-muted);
}

.ds-reason-name .t-row-primary,
.ds-reason-name .t-row-secondary {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ds-case-event.ds-value-flat {
  color: var(--text-muted);
}

/* Four facts, and two of them decide what a human does next. */
.ds-facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1px solid var(--border-hairline);
}

.ds-fact {
  padding: 13px 0 14px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.ds-fact + .ds-fact {
  padding-left: 22px;
  border-left: 1px solid var(--border-hairline);
}

.ds-btn.is-small {
  padding: 4px 9px;
  font-size: 12.5px;
  border-radius: var(--r-control);
}

.ds-card > .ds-list-grid > .ds-row-list:last-child {
  padding-bottom: 10px;
}

/* Red belongs to what takes something away — a contract, a payment, an access
 * card. Add credit keeps the plain navy primary: a destructive-looking button
 * on a generous action teaches people to distrust the colour. */
.ds-btn-primary.is-danger {
  background: var(--bad-text);
  border-color: var(--bad-text);
}

.ds-btn-primary.is-danger:hover {
  background: var(--bad-strong);
  border-color: var(--bad-strong);
}

.ds-btn-primary[disabled] {
  background: var(--border-strong);
  border-color: var(--border-strong);
  color: var(--bg-surface);
  cursor: not-allowed;
}

.ds-fact {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.ds-fact-row > .t-row-secondary {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A value with no spaces in it — a Firestore path, an idempotency key — has no
 * place to break, so it ran off the right edge of the page instead of wrapping
 * inside its column. Anywhere is the only break rule that holds for a machine
 * string. */
.ds-cell.is-wrap .t-row-primary,
.ds-cell.is-wrap .t-row-secondary {
  overflow-wrap: anywhere;
}

/* A badge in a list cell hugs its own text. The cell is a column, and a column
 * stretches its children by default — which turned every KIND badge on the
 * history page into a 190px block of colour with three words in the middle of
 * it. A badge is a label, not a bar. */
.ds-cell > .ds-badge {
  align-self: flex-start;
}

.ds-cell.is-right > .ds-badge {
  align-self: flex-end;
}

/* A state badge in the card header never wraps: "CANCELS 14.11." on two lines
 * reads as two badges. */
.ds-sub-state .ds-badge {
  white-space: nowrap;
}

.ds-sub .ds-row-menu {
  top: 60px;
  right: 14px;
}
