/*
 * A VERBATIM COPY of admin/styles.css — Slab Soft, as the console wears it.
 *
 * Two static sites with no build step cannot share a stylesheet without one of them
 * fetching from the other origin, which is a dependency on somebody else being up and a
 * hole in a CSP that is otherwise `default-src 'none'`. Copying 500 lines of CSS is the
 * cheaper half of that trade.
 *
 * Keep them identical. A vendor portal that looks like a different product is one a
 * business trusts less, and these two are the same product seen from two sides.
 */

/*
 * Slab Soft, restated for a browser — the "Ink Rail" console shell.
 *
 * These values are copied from theme/tokens.ts, not imported from it — the console is a
 * separate plain web app with no build step and no React, so there is nothing to import
 * through. Two consequences worth being honest about:
 *
 *   · CLAUDE.md's "no literal hex in a screen or component" is about the app, where a token
 *     file exists to hold them. Here the custom properties below ARE that file: every colour,
 *     radius and space is defined once, at the top, and nothing further down writes a literal.
 *     A visual change is still a token change.
 *
 *   · When theme/tokens.ts moves, this does not follow automatically. It is a staff tool, so
 *     drifting a shade is survivable in a way it would not be in the product.
 *
 * The one thing worth keeping in step is the meaning, not the value: orange means "this wants
 * you", and nothing else is ever orange.
 *
 * ============================================================
 * why the rail is ink, and why that is not decoration
 * ============================================================
 * Whoever is holding this page is holding a session that can suspend an account and find
 * anyone by phone number. A full-height ink rail means a staff window is never mistaken for a
 * product window at a glance, in a tab strip, or in a screenshot. It costs one colour.
 *
 * The rail's colours are the DARK-theme surfaces already defined below, used in a light page
 * rather than waiting for the whole page to go dark. So there is one palette, not two.
 */

:root {
  --bg: #edeae4;
  --surface: #ffffff;
  --surface2: #f7f4ee;
  --line: #e4dfd5;

  --ink: #1c1b20;
  --ink2: #6e6a64;
  --ink3: #a19c93;

  --accent: #ff5a1f;
  --accent-ink: #c43f0c;
  --accent-wash: #ffebe1;
  --on-accent: #ffffff;

  --info: #2f4bd8;
  --info-wash: #e6eaff;

  /* "Done, and nothing wants you." Never used for an action — only for a resting good state. */
  --good-ink: #1f7a4d;
  --good-wash: #e4f5ec;

  /* The rail. Fixed across both themes: it is ink in a light page and ink in a dark one. */
  --rail: #1c1b20;
  --rail2: #26242c;
  --rail-line: #322f38;
  --rail-ink: #efebe4;
  --rail-ink2: #8f8a82;
  --rail-ink3: #6b665f;
  --rail-on: #ffffff;

  /* Warm, never neutral black. The single choice that stops this reading as stock Material. */
  --shadow: 60 42 24;

  --r-inner: 10px;
  --r-card: 14px;
  --r-pill: 999px;

  --s-xs: 4px;
  --s-sm: 8px;
  --s-md: 12px;
  --s-lg: 16px;
  --s-xl: 24px;
  --s-xxl: 32px;

  --rail-w: 230px;

  --body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #141317;
    --surface: #1f1d23;
    --surface2: #26242c;
    --line: #2c2a31;

    --ink: #efebe4;
    --ink2: #8f8a82;
    --ink3: #6b665f;

    --accent-ink: #ff7a45;
    --accent-wash: #33211a;

    --info-wash: #1d2140;
    --good-ink: #56c48d;
    --good-wash: #15271e;

    /* The rail has to stay distinguishable from the working surface, which is now also dark. */
    --rail: #0e0d11;
    --rail2: #1c1b20;
    --rail-line: #26242c;

    --shadow: 0 0 0;
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 14px/1.5 var(--body);
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------- the shell */

/*
  `position: relative` is load-bearing. It is what the mobile drawer is positioned against, so
  a drawer can never escape to cover a page it does not own.
*/
.shell {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  min-height: 100vh;
  position: relative;
}

/* ---------------------------------------------------------------- the rail */

.rail {
  background: var(--rail);
  color: var(--rail-ink);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
}

/* ---------------------------------------------------------------- narrowing the rail */

/*
  Icons only, 64px. Nineteen destinations is a lot of rail to look at all day when you are
  working inside one table, so this gives the table back 166px — and it is remembered, because
  somebody who wants it narrow wants it narrow tomorrow too.

  Everything that disappears is a WORD. Nothing is removed from the rail, the tallies stay
  (as dots), and every link keeps a `title`, so the narrow state is a smaller version of the
  same navigation rather than a different one.
*/
.shell.rail-narrow {
  grid-template-columns: 64px minmax(0, 1fr);
}

.shell.rail-narrow .nav a span,
.shell.rail-narrow .group-label,
.shell.rail-narrow .brand .name,
.shell.rail-narrow .brand .who,
.shell.rail-narrow .whoami,
.shell.rail-narrow .railfoot .out {
  display: none;
}

.shell.rail-narrow .nav a {
  justify-content: center;
  padding: 0;
  position: relative;
  min-height: 40px;
}

.shell.rail-narrow .brand,
.shell.rail-narrow .railfoot {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}

/* A group with its label hidden still needs to read as a group. */
.shell.rail-narrow .group + .group {
  margin-top: var(--s-sm);
  padding-top: var(--s-sm);
  border-top: 1px solid var(--rail-line);
}

/* The count becomes a dot. The number is unreadable at this width, and "something is waiting"
   is the part that has to survive. */
.shell.rail-narrow .tally {
  position: absolute;
  top: 6px;
  right: 12px;
  width: 7px;
  height: 7px;
  min-width: 0;
  padding: 0;
  border-radius: 50%;
  font-size: 0;
  line-height: 0;
  overflow: hidden;
}

/* A tab on the rail's own edge. It belongs to neither column, which is why it is not competing
   with the brand or the sign-out button for space in a 64px rail. */
.rail-toggle {
  position: absolute;
  top: 68px;
  right: -11px;
  width: 22px;
  height: 22px;
  min-height: 22px;
  padding: 0;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink2);
  display: grid;
  place-content: center;
  z-index: 15;
  box-shadow: 0 1px 3px rgb(var(--shadow) / 0.2);
}

.rail-toggle:hover {
  color: var(--ink);
  border-color: var(--ink3);
}

.shell.rail-narrow .rail-toggle svg {
  transform: scaleX(-1);
}

/* ---------------------------------------------------------------- the phone field */

/*
  A calling code and a number, on one line, reading as a single control.

  The select is sized to its content rather than sharing the row evenly: "+1" and "+506" are
  short, and a half-width dropdown next to a half-width number field makes the number — the
  part somebody is actually typing — the smaller of the two.
*/
.phone-row {
  display: flex;
  gap: var(--s-sm);
}

.phone-row select {
  width: auto;
  flex: 0 0 auto;
  max-width: 44%;
}

.phone-row input {
  flex: 1;
  min-width: 0;
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--s-sm);
  padding: 17px var(--s-lg);
  border-bottom: 1px solid var(--rail-line);
  flex: none;
}

/* The mark is four people on a slab — assets/logo/README.md has the geometry and the rules.
   Ink people on light, white people on the rail; the slab is orange in both. */
.mark {
  width: 26px;
  height: 14.5px;
  flex: none;
  color: var(--ink);
}

.rail .mark,
.appbar .mark {
  color: var(--rail-on);
}

.name {
  font-weight: 700;
  font-size: 17px;
  letter-spacing: -0.4px;
}

.rail .name,
.appbar .name {
  color: var(--rail-on);
}

/* Which of the three apps this is. Small, and the only orange in the rail's header. */
.who {
  font-family: var(--mono);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 1.3px;
  color: var(--accent);
  border: 1px solid rgb(255 90 31 / 42%);
  border-radius: var(--r-pill);
  padding: 2px 6px;
  margin-left: auto;
}

/*
  min-height: 0 is load-bearing. A flex child will not shrink below its content height without
  it, so with thirteen nav items the rail grows past the viewport instead of scrolling inside
  it — and the sign-out button at the foot goes with it.
*/
.railscroll {
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  padding: 10px 0;
  /* Firefox. The rail is ink; a default scrollbar puts a bright white gutter down it. */
  scrollbar-width: thin;
  scrollbar-color: var(--rail-line) transparent;
}

/* WebKit and Blink say the same thing a different way. */
.railscroll::-webkit-scrollbar {
  width: 8px;
}

.railscroll::-webkit-scrollbar-track {
  background: transparent;
}

.railscroll::-webkit-scrollbar-thumb {
  background: var(--rail-line);
  border-radius: var(--r-pill);
}

.railscroll::-webkit-scrollbar-thumb:hover {
  background: var(--rail-ink3);
}

.group + .group {
  margin-top: var(--s-md);
}

.group-label {
  font-family: var(--mono);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--rail-ink3);
  padding: 0 var(--s-lg) 5px;
  margin: 0;
}

.nav {
  display: flex;
  flex-direction: column;
}

.nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 var(--s-lg);
  min-height: 34px;
  color: var(--rail-ink2);
  text-decoration: none;
  font-weight: 500;
  font-size: 13px;
  border-left: 2.5px solid transparent;
}

.nav a:hover {
  background: var(--rail2);
  color: var(--rail-ink);
}

/* Orange still means "this one", and on ink it can be a hairline rather than a wash — which is
   what lets a rail hold thirteen items and stay quiet. */
.nav a.here {
  background: var(--rail2);
  color: var(--rail-on);
  border-left-color: var(--accent);
  font-weight: 600;
}

.nav .ico {
  width: 15px;
  height: 15px;
  flex: none;
  opacity: 0.72;
}

.nav a.here .ico {
  opacity: 1;
  color: var(--accent);
}

/* A count of things waiting for a person. Absent, not zero, when nothing is waiting. */
.tally {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  background: var(--accent);
  color: var(--on-accent);
  border-radius: var(--r-pill);
  padding: 1px 6px;
  min-width: 19px;
  text-align: center;
}

.railfoot {
  border-top: 1px solid var(--rail-line);
  padding: 11px var(--s-lg);
  display: flex;
  align-items: center;
  gap: 9px;
  flex: none;
}

.avatar {
  width: 28px;
  height: 28px;
  border-radius: 9px;
  background: var(--rail2);
  border: 1px solid var(--rail-line);
  color: var(--rail-on);
  display: grid;
  place-content: center;
  font-weight: 700;
  font-size: 12px;
  flex: none;
}

/* The same square, used in the page as a marker rather than in the rail as a person. Orange,
   because it only ever labels something that wants somebody. */
.avatar.warn {
  background: var(--accent-wash);
  border-color: transparent;
  color: var(--accent-ink);
}

.railfoot .whoami {
  min-width: 0;
  line-height: 1.25;
}

.railfoot .whoami b {
  display: block;
  font-size: 12.5px;
  color: var(--rail-on);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Three roles is a real roster, not a corner case — Rosa holds all of them. Ellipsis rather
   than wrap, or the footer grows and pushes itself off the bottom of the rail. */
.railfoot .whoami span {
  display: block;
  font-family: var(--mono);
  font-size: 9.5px;
  color: var(--accent);
  letter-spacing: 0.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.railfoot .out {
  margin-left: auto;
  background: none;
  border: 0;
  padding: 6px;
  min-height: 0;
  cursor: pointer;
  color: var(--rail-ink3);
  flex: none;
}

.railfoot .out:hover {
  color: var(--rail-on);
}

/* ---------------------------------------------------------------- the working surface */

.view {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/*
  A real page bar: the title, and a fixed home for the screen's own actions. Sticky, because
  the tables under it are long and "where am I" should not need a scroll to the top.
*/
.pagebar {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: var(--s-md);
  padding: var(--s-md) 22px;
  min-height: 60px;
  flex-wrap: wrap;
}

.pagebar h1 {
  font-size: 20px;
  letter-spacing: -0.4px;
  margin: 0;
}

.crumb {
  display: block;
  font-family: var(--mono);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--ink3);
  margin-bottom: 1px;
}

.pagebar .right {
  margin-left: auto;
  display: flex;
  gap: var(--s-sm);
  align-items: center;
  flex-wrap: wrap;
}

.body {
  padding: 18px 22px 26px;
  display: flex;
  flex-direction: column;
  gap: var(--s-lg);
  min-width: 0;
}

/* The screens that stand on their own — sign-in, not-configured, nothing-here-for-you. */
.wrap {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 var(--s-lg);
}

main.wrap {
  padding-top: var(--s-xl);
  padding-bottom: var(--s-xxl);
}

/* ---------------------------------------------------------------- the mobile app bar */

/* Ink, so "this is the staff tool" survives a 390px screen where the rail is hidden. */
.appbar {
  display: none;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  background: var(--rail);
  color: var(--rail-on);
  position: sticky;
  top: 0;
  z-index: 12;
}

.burger {
  width: 36px;
  height: 36px;
  min-height: 36px;
  border-radius: 9px;
  border: 1px solid var(--rail-line);
  background: var(--rail2);
  color: var(--rail-on);
  display: grid;
  place-content: center;
  cursor: pointer;
  flex: none;
  padding: 0;
}

/* A grid child, so without this the scrim silently eats a column on desktop. */
.scrim {
  display: none;
}

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

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--s-lg);
}

.row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-md);
  flex-wrap: wrap;
}

.row.tight {
  justify-content: flex-end;
  gap: var(--s-sm);
}

/* A row of things that belong together on the left, rather than pushed to both ends. */
.row.start {
  justify-content: flex-start;
  align-items: flex-start;
  gap: 10px;
}

.grow {
  flex: 1;
  min-width: 0;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--s-md);
}

/* The dashboard's shape: a wide column of work and a narrow column about it. */
.cols {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: var(--s-lg);
  align-items: start;
}

.cols > .stack {
  gap: var(--s-lg);
}

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

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: var(--s-lg);
  box-shadow: 0 1px 2px rgb(var(--shadow) / 0.06), 0 4px 12px rgb(var(--shadow) / 0.06);
}

.card > * + * {
  margin-top: var(--s-md);
}

/*
  A card that is a form or a settings panel rather than a report — the signup schedule, the
  pricing form, a key editor, a vendor's own status while it is not yet active. Inset instead
  of raised: it sits in the page rather than floating on it.
*/
.card.editor {
  background: var(--surface2);
  box-shadow: none;
}

/*
  A titled region with its own header and footer rules. The dashboard and every list screen use
  it; `.card` remains what a single block of prose or a form goes in.
*/
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: 0 1px 2px rgb(var(--shadow) / 0.06), 0 4px 12px rgb(var(--shadow) / 0.06);
  overflow: hidden;
}

.panel > header {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: var(--s-md) var(--s-lg);
  border-bottom: 1px solid var(--line);
  background: var(--surface2);
  flex-wrap: wrap;
}

.panel > header h2,
.panel > header h3 {
  margin: 0;
}

.panel > header .right {
  margin-left: auto;
  display: flex;
  gap: 6px;
  align-items: center;
}

.panel > footer {
  padding: 10px var(--s-lg);
  border-top: 1px solid var(--line);
  background: var(--surface2);
  font-size: 12.5px;
  color: var(--ink2);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.panel > footer a {
  color: var(--accent-ink);
  font-weight: 600;
  text-decoration: none;
  margin-left: auto;
}

.padded {
  padding: var(--s-lg);
}

.padded > * + * {
  margin-top: var(--s-md);
}

.sign-in {
  max-width: 380px;
  margin: 10vh auto 0;
}

/* The mark, above the sign-in card. The one place either app introduces itself. */
.sign-in-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-sm);
  max-width: 380px;
  margin: 10vh auto 0;
}

/* The second selector is not redundant. On the vendor sign-up the card is wrapped in a
   `<main class="wrap">`, so it is not the mark's adjacent sibling and would keep its own 10vh. */
.sign-in-mark + .sign-in,
.sign-in-mark + main .sign-in {
  margin-top: var(--s-lg);
}

/* In the rail the badge is pushed to the far end by `margin-left: auto`. Here the row is only
   as wide as the card, and an orange lozenge floating 200px from the wordmark reads as a
   second, unrelated thing. */
.sign-in-mark .who {
  margin-left: 0;
}

/* ---------------------------------------------------------------- type */

h1 {
  font-size: 22px;
  letter-spacing: -0.4px;
  margin: 0;
}

h2 {
  font-size: 15px;
  letter-spacing: -0.2px;
  margin: 0;
}

h3 {
  font-size: 13px;
  margin: 0;
}

p {
  margin: 0;
}

.muted {
  color: var(--ink2);
}

.hint {
  color: var(--ink3);
  font-size: 12px;
  max-width: 62ch;
}

.small {
  font-size: 12px;
}

.mono {
  font-family: var(--mono);
  font-size: 12px;
}

pre.mono {
  background: var(--surface2);
  padding: var(--s-md);
  border-radius: var(--r-inner);
  overflow-x: auto;
}

.empty {
  color: var(--ink2);
  padding: var(--s-xl);
  text-align: center;
  background: var(--surface2);
  border-radius: var(--r-inner);
}

.problem {
  color: var(--accent-ink);
  background: var(--accent-wash);
  padding: var(--s-md);
  border-radius: var(--r-inner);
}

/* A refusal is the system working. It should not shout like a crash. */
.problem.quiet {
  color: var(--ink2);
  background: var(--surface2);
}

/* ---------------------------------------------------------------- fields */

.field {
  display: block;
}

.field + .field,
.field + .actions {
  margin-top: var(--s-md);
}

.field-label {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: var(--ink3);
  margin-bottom: var(--s-xs);
}

/*
  A field looks like somewhere you can type.

  It used to take `--surface2`, which is also the background of `.card.editor` — so on every
  settings form the input was the same colour as the card behind it, with a hairline border,
  and the whole screen read as greyed out. Filled-grey is the convention for DISABLED, and
  using it for the enabled state left nothing to say disabled with.

  So: the surface colour, a border that is visible on both, and `--surface2` given back to
  `:disabled` where it means what people expect it to mean.
*/
input,
select,
textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-inner);
  padding: 10px var(--s-md);
  /* docs/11: hit targets ≥ 44pt. A staff tool is not exempt. */
  min-height: 44px;
  transition: border-color 0.12s ease;
}

/* A form inside an inset panel needs a stronger edge — the card is already off-white. */
.card.editor input,
.card.editor select,
.card.editor textarea {
  border-color: var(--ink3);
}

input:hover:not(:disabled),
select:hover:not(:disabled),
textarea:hover:not(:disabled) {
  border-color: var(--ink3);
}

input::placeholder,
textarea::placeholder {
  color: var(--ink3);
}

input:disabled,
select:disabled,
textarea:disabled {
  background: var(--surface2);
  color: var(--ink3);
  cursor: not-allowed;
}

select {
  cursor: pointer;
}

textarea {
  font-family: var(--mono);
  font-size: 12px;
  resize: vertical;
}

input:focus,
select:focus,
textarea:focus,
button:focus-visible,
a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
}

/*
  A checkbox is not a text field, and the rule above is written for text fields. Without this
  reset it inherits `width: 100%` and `min-height: 44px` and renders as a 44px-tall blue slab
  across the whole form — which is exactly what the Sign-ups card was showing.
*/
input[type="checkbox"],
input[type="radio"] {
  width: 18px;
  height: 18px;
  min-height: 18px;
  flex: none;
  margin: 0;
  padding: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

/* The label sits beside the box, not above it, and the whole row is the hit target. */
.check {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  cursor: pointer;
  font-weight: 500;
}

.check:has(input:disabled) {
  cursor: not-allowed;
  color: var(--ink3);
}

/* The pagebar's search box is a field that has to sit on one line with buttons. */
.seek {
  min-height: 38px;
  min-width: 190px;
  border-radius: var(--r-pill);
  padding: 0 var(--s-md);
  width: auto;
  flex: 1;
  max-width: 300px;
}

/* ---------------------------------------------------------------- buttons */

button {
  font: inherit;
  font-weight: 600;
  font-size: 13px;
  border-radius: var(--r-inner);
  border: 1px solid transparent;
  padding: 0 var(--s-lg);
  min-height: 40px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

button:disabled {
  opacity: 0.45;
  cursor: default;
}

.primary {
  background: var(--accent);
  color: var(--on-accent);
}

.secondary {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--line);
}

.danger {
  background: transparent;
  color: var(--accent-ink);
  border-color: currentColor;
}

.link {
  background: none;
  color: var(--accent-ink);
  padding: 0 var(--s-sm);
  min-height: 32px;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.tiny {
  min-height: 30px;
  padding: 0 11px;
  font-size: 12px;
}

.actions {
  display: flex;
  gap: var(--s-sm);
  align-items: center;
  flex-wrap: wrap;
}

/*
  In a table, the actions cell is a TABLE CELL — not a flex container.

  `.actions` above sets `display: flex`, which on a `<td>` takes the cell out of the table's
  column model entirely. With only links in it that went unnoticed; add a 44px switch and the
  cell stops being a column and floats past the right-hand edge of the table it belongs to.

  So the display is put back, the children are laid out inline instead, and `width: 1%` is the
  usual trick for "this column takes only what it needs". `nowrap` is what keeps the Edit link
  and the switch on one line: wrapping put the switch underneath, which doubled every row's
  height and broke the one thing a switch is for — reading a column of them down the page.
*/
td.actions {
  display: table-cell;
  white-space: nowrap;
  width: 1%;
}

td.actions > * {
  vertical-align: middle;
}

td.actions > * + * {
  margin-left: var(--s-sm);
}

/* ==========================================================================
   THE SWITCH

   It replaces a button labelled "Switch off". That button named what would happen NEXT, so a
   table of forty had to be read word by word to learn what was currently on. This shows state,
   and one glance down the column answers the question the screen exists to answer.

   ON is ink, not orange, and that is the point: almost everything in this console is on, and a
   column of forty orange lozenges would drain the one colour that means "this wants you".

   It stays a real <button role="switch" aria-checked>, so a screen reader says "Casa Verde,
   switch, on" and the space bar flips it. The ::before grows the hit target to 44px without
   growing the picture — docs/11 asks for 44 and a staff tool is not exempt.
   ========================================================================== */

.switch {
  position: relative;
  width: 44px;
  height: 26px;
  min-height: 26px;
  padding: 0;
  flex: none;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--surface2);
  cursor: pointer;
  transition: background 0.16s ease, border-color 0.16s ease;
  vertical-align: middle;
}

.switch::before {
  content: "";
  position: absolute;
  inset: -9px;
  border-radius: var(--r-pill);
}

.switch .knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 1px 3px rgb(var(--shadow) / 0.3);
  transition: transform 0.16s ease;
}

.switch[aria-checked="true"] {
  background: var(--ink);
  border-color: var(--ink);
}

.switch[aria-checked="true"] .knob {
  transform: translateX(18px);
}

.switch:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.switch:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Mid-flight. The row keeps its old position until the server agrees. */
.switch.busy {
  opacity: 0.55;
  cursor: progress;
}

@media (prefers-reduced-motion: reduce) {
  .switch,
  .switch .knob,
  .rail {
    transition: none;
  }
}

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

.scroll {
  overflow-x: auto;
}

/* A bare table still needs its own surface when it is not inside a panel. */
.scroll:not(.panel .scroll) {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: 0 1px 2px rgb(var(--shadow) / 0.06), 0 4px 12px rgb(var(--shadow) / 0.06);
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

th {
  text-align: left;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--ink3);
  font-weight: 400;
  padding: var(--s-sm) var(--s-lg);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

td {
  padding: 9px var(--s-lg);
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}

tbody tr:last-child td {
  border-bottom: none;
}

td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

td.mid {
  text-align: center;
}

tr.off td {
  opacity: 0.5;
}

tr.editing td {
  background: var(--surface2);
  padding: var(--s-md);
}

tr.editing .card {
  box-shadow: none;
}

/* ---------------------------------------------------------------- the lightbox */

/*
  One image over everything else. Fixed rather than absolute so it does not move when the table
  behind it scrolls, and a grid so the picture centres without any arithmetic.
*/
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-content: center;
  gap: var(--s-md);
  padding: var(--s-lg);
  /* Dark enough that a pale product photograph still reads as lifted off the page. */
  background: rgb(0 0 0 / 72%);
}

.lightbox-frame {
  position: relative;
  max-width: min(1100px, 92vw);
  /* Room for the caption under it, so a tall image does not push its own label off-screen. */
  max-height: 84vh;
  border-radius: var(--r-card);
  background: var(--surface2);
  overflow: hidden;
}

.lightbox-frame img {
  display: block;
  max-width: min(1100px, 92vw);
  max-height: 84vh;
  /* `contain`, not `cover`: this is the view somebody opened to see the WHOLE picture, which
     is the opposite of what the thumbnail wants. */
  object-fit: contain;
}

.lightbox-caption {
  margin: 0;
  max-width: min(1100px, 92vw);
  color: #fff;
  font-size: 13px;
  text-align: center;
}

.lightbox-close {
  position: absolute;
  top: var(--s-sm);
  right: var(--s-sm);
  width: 32px;
  min-height: 32px;
  padding: 0;
  font-size: 20px;
  line-height: 1;
  border-radius: var(--r-pill);
  background: var(--surface);
  border: 1px solid var(--line);
}

/* ---------------------------------------------------------------- thumbnails */

/*
  The creative, small, in a catalogue table.

  A fixed box rather than a max-width: these are other people's product photographs at every
  aspect ratio there is, and a column that changed width per row would make eight thousand rows
  unscannable. `object-fit: cover` crops rather than squashes — a squashed thumbnail looks like
  a rendering fault and a cropped one reads as a thumbnail.

  It is a <button>, so the base button rule reaches it first: min-height, padding and a pill
  radius, all of which would squash a 56×42 picture into a lozenge with margins. Each one is
  reset here deliberately rather than by raising specificity somewhere else.
*/
.thumb {
  display: block;
  width: 56px;
  height: 42px;
  min-height: 42px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-inner);
  background: var(--surface2);
  overflow: hidden;
}

.thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The column sizes to the thumbnail and stops the title column giving up space for it. */
td.thumb-cell {
  width: 56px;
  padding-right: 0;
}

/* ---------------------------------------------------------------- pills */

.pill {
  display: inline-block;
  padding: 2px 9px;
  margin-left: var(--s-xs);
  border-radius: var(--r-pill);
  background: var(--surface2);
  border: 1px solid var(--line);
  font-size: 11px;
  font-weight: 600;
  color: var(--ink2);
  white-space: nowrap;
}

tbody .pill:first-child,
.field + .pill,
.panel > header .pill,
.row .pill {
  margin-left: 0;
}

/* Orange means "this wants you" — a suspension, a live role, a queue with someone in it. It is
   never decoration. */
.pill.hot {
  background: var(--accent-wash);
  border-color: transparent;
  color: var(--accent-ink);
}

/* Blue means "this is new", matching the badge rule in docs/14. */
.pill.new {
  background: var(--info-wash);
  border-color: transparent;
  color: var(--info);
}

/* Green means "resting, and nothing wants you". Never an action. */
.pill.ok {
  background: var(--good-wash);
  border-color: transparent;
  color: var(--good-ink);
}

/* ---------------------------------------------------------------- facts */

.facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-sm) var(--s-lg);
  margin: 0;
}

.facts.wide {
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--s-lg);
}

.facts.wide dt {
  grid-row: 1;
}

.facts.wide dd {
  grid-row: 2;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.4px;
}

dt {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: var(--ink3);
}

dd {
  margin: 0;
}

.found {
  margin-top: var(--s-lg);
  padding-top: var(--s-lg);
  border-top: 1px solid var(--line);
}

.found > * + * {
  margin-top: var(--s-md);
}

/* ==========================================================================
   THE DASHBOARD
   ========================================================================== */

/*
  One card divided by hairlines, not five floating tiles. Denser, and it reads as a single
  instrument panel — which is what a back office wants and a consumer app does not.
*/
.strip {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: 0 1px 2px rgb(var(--shadow) / 0.06), 0 4px 12px rgb(var(--shadow) / 0.06);
  overflow: hidden;
  margin: 0;
}

.strip > div {
  padding: 14px var(--s-lg);
  border-left: 1px solid var(--line);
  min-width: 0;
}

.strip > div:first-child {
  border-left: 0;
}

.strip dt {
  font-size: 9px;
  letter-spacing: 1.4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.strip dd {
  margin-top: 5px;
  font-size: 23px;
  font-weight: 700;
  letter-spacing: -0.6px;
  line-height: 1.05;
}

.strip .under {
  font-size: 11px;
  font-weight: 600;
  margin-top: 3px;
  color: var(--ink3);
}

/* A figure with a person waiting behind it. The only orange on the dashboard, and it is absent
   when the count is zero — a queue of nothing does not want anybody. */
.strip .wants dd,
.strip .wants dt,
.strip .wants .under {
  color: var(--accent-ink);
}

.strip .wants dt {
  opacity: 0.75;
}

/* A link that covers its whole tile, without turning the tile into an <a> full of block
   elements. */
.strip a.tile {
  color: inherit;
  text-decoration: none;
  display: block;
}

.strip a.tile:hover dd {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Three charts side by side, one measure each — never one chart with three scales. */
.cols3 {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-lg);
  align-items: start;
}

/*
  A day per bar, from zero.

  `align-items: flex-end` is what anchors every bar to the baseline, and the baseline is zero
  rather than the smallest value — a bar chart that starts at the minimum makes a 2% difference
  look like a cliff, which is the most common way a chart lies.

  The 2px gap is the surface showing between marks rather than a border on them, so adjacent
  bars stay separate without a colour of their own.
*/
.bars {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 92px;
}

.bars i {
  flex: 1;
  min-width: 2px;
  background: var(--ink3);
  /* Rounded at the data end only. The baseline end stays square, because a rounded foot lifts
     the mark off the axis it is measured from. */
  border-radius: 4px 4px 0 0;
  transition: background 0.12s ease;
}

.bars i:hover {
  background: var(--ink2);
}

/* One direct label instead of a number on every bar. */
.bars i.peak {
  background: var(--accent);
}

.legend .grow {
  flex: 1;
}

/* Seven days is seven bars. A sparkline would imply a trend seven points cannot carry. */
.spark {
  display: flex;
  align-items: flex-end;
  gap: 2.5px;
  height: 22px;
  margin-top: 7px;
}

.spark i {
  flex: 1;
  background: var(--line);
  border-radius: 2px 2px 0 0;
  min-height: 2px;
}

.spark i.on {
  background: var(--ink3);
}

/* Today, and only today. */
.spark i:last-child {
  background: var(--accent);
}

/* A proportion bar — listings by state, credit remaining. */
.bar {
  height: 7px;
  border-radius: var(--r-pill);
  background: var(--surface2);
  overflow: hidden;
  display: flex;
}

.bar i {
  display: block;
  height: 100%;
}

.legend {
  display: flex;
  gap: 13px;
  flex-wrap: wrap;
  font-size: 11.5px;
  color: var(--ink2);
}

.legend span {
  display: flex;
  align-items: center;
  gap: 5px;
}

.legend b {
  width: 8px;
  height: 8px;
  border-radius: 3px;
  display: inline-block;
  flex: none;
}

/* The audit feed, and the vendor portal's "what you can do now". */
.feed {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
}

.feed li {
  display: flex;
  gap: 10px;
  padding: 9px var(--s-lg);
  border-bottom: 1px solid var(--line);
  list-style: none;
}

.feed li:last-child {
  border-bottom: 0;
}

.feed .act {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--ink2);
  background: var(--surface2);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 1px 5px;
  height: fit-content;
  flex: none;
}

.feed .said {
  min-width: 0;
  font-size: 12.5px;
}

.feed .when {
  color: var(--ink3);
  font-size: 11px;
}

/* Where an account stands, in a sentence, above everything else on its own screen. */
.banner {
  border-radius: var(--r-card);
  padding: var(--s-lg);
  display: flex;
  gap: 13px;
  align-items: flex-start;
  border: 1px solid var(--line);
  background: var(--surface);
  box-shadow: 0 1px 2px rgb(var(--shadow) / 0.06), 0 4px 12px rgb(var(--shadow) / 0.06);
}

.banner.waiting {
  background: var(--accent-wash);
  border-color: transparent;
  color: var(--accent-ink);
}

.banner .icon {
  width: 36px;
  height: 36px;
  border-radius: var(--r-inner);
  display: grid;
  place-content: center;
  flex: none;
  background: var(--good-wash);
  color: var(--good-ink);
}

.banner.waiting .icon {
  background: rgb(255 255 255 / 55%);
  color: var(--accent-ink);
}

.banner h2 {
  font-size: 16px;
  letter-spacing: -0.3px;
}

.banner p {
  margin-top: 3px;
  font-size: 12.5px;
}

.money {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.6px;
}

/* ==========================================================================
   MOBILE
   ========================================================================== */

@media (max-width: 860px) {
  /*
    `.shell.rail-narrow` is listed too, and it has to be. A media query does not raise
    specificity, so the (0,2,0) of the narrow rule would beat a bare `.shell` here and pin a
    phone to a 64px first column with the drawer sliding over it.
  */
  .shell,
  .shell.rail-narrow {
    grid-template-columns: minmax(0, 1fr);
  }

  /* There is no rail to narrow when the rail is a drawer. */
  .rail-toggle {
    display: none;
  }

  .shell.rail-narrow .nav a span,
  .shell.rail-narrow .group-label,
  .shell.rail-narrow .brand .name,
  .shell.rail-narrow .brand .who,
  .shell.rail-narrow .whoami,
  .shell.rail-narrow .railfoot .out {
    display: revert;
  }

  .shell.rail-narrow .nav a {
    justify-content: flex-start;
    padding: 0 var(--s-lg);
  }

  .shell.rail-narrow .brand,
  .shell.rail-narrow .railfoot {
    justify-content: flex-start;
    padding-left: var(--s-lg);
    padding-right: var(--s-lg);
  }

  .shell.rail-narrow .tally {
    position: static;
    width: auto;
    height: auto;
    min-width: 19px;
    padding: 1px 6px;
    border-radius: var(--r-pill);
    font-size: 10px;
    line-height: normal;
  }

  .appbar {
    display: flex;
  }

  /*
    The rail becomes a full-height drawer, still ink edge to edge. `absolute` against the shell
    rather than `fixed` against the viewport: the shell is the full-height app shell, so the
    drawer lands in the same place either way, and absolute cannot escape to cover a page it
    does not own.
  */
  .rail {
    position: absolute;
    inset: 0 auto 0 0;
    width: 272px;
    height: auto;
    z-index: 30;
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    box-shadow: 0 0 40px rgb(0 0 0 / 35%);
  }

  .shell.drawer-open .rail {
    transform: none;
  }

  .shell.drawer-open .scrim {
    display: block;
    position: absolute;
    inset: 0;
    background: rgb(28 27 32 / 50%);
    z-index: 20;
  }

  .pagebar {
    padding: 11px 14px;
    min-height: 54px;
  }

  .pagebar h1 {
    font-size: 18px;
  }

  .pagebar .right {
    margin-left: 0;
    width: 100%;
  }

  .seek {
    max-width: none;
  }

  .body {
    padding: var(--s-md) 14px 20px;
    gap: var(--s-md);
  }

  /* Five across becomes two, and the "wants you" figures are first in the source order, so
     they are the two above the fold. */
  .strip {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .strip > div {
    border-left: 1px solid var(--line);
    border-top: 1px solid var(--line);
  }

  .strip > div:nth-child(-n + 2) {
    border-top: 0;
  }

  .strip > div:nth-child(odd) {
    border-left: 0;
  }

  .cols,
  .cols3 {
    grid-template-columns: minmax(0, 1fr);
  }

  .panel > header,
  .padded,
  .card {
    padding: 14px;
  }

  th,
  td {
    padding: var(--s-sm) 14px;
  }

  .feed li {
    padding: var(--s-sm) 14px;
  }

  /*
    A table of nine columns is not a mobile screen. The same rows are re-laid as stacked
    records — same data, same order, no horizontal scrolling.

    Flex, not grid, and the `order` values are the reason. A record is two visual lines,
    [title ......... action] then [meta meta meta], and in grid the action cell has to be
    placed explicitly, which puts it BEFORE the auto-placed title and lands the button on its
    own line above the name. The ::after is a zero-height flex item whose only job is to force
    the wrap, so the two lines hold however many meta cells a table happens to have.
  */
  table.stacky thead {
    display: none;
  }

  table.stacky tr {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 10px;
    row-gap: 5px;
    padding: 11px 14px;
    border-bottom: 1px solid var(--line);
  }

  table.stacky td {
    display: block;
    padding: 0;
    border: 0;
  }

  table.stacky td.num,
  table.stacky td.mid {
    text-align: left;
  }

  table.stacky td[data-k]::before {
    content: attr(data-k) " ";
    font-family: var(--mono);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 1.3px;
    color: var(--ink3);
  }

  table.stacky td.span {
    order: 1;
    flex: 1 1 0;
    min-width: 0;
  }

  /* `width: 1%` is what shrinks the actions column to its contents in a real table. Here the
     cell is a flex item, so that 1% becomes its flex base — about four pixels — and the Edit
     link and the switch spill out of it and get clipped. */
  table.stacky td.rightcell,
  table.stacky td.actions {
    order: 2;
    flex: none;
    width: auto;
  }

  table.stacky tr::after {
    content: "";
    order: 3;
    flex-basis: 100%;
    height: 0;
  }

  table.stacky td:not(.span):not(.rightcell) {
    order: 4;
    flex: none;
  }

  /* An empty trailing cell would otherwise sit on the meta line as a phantom gap. */
  table.stacky td:empty {
    display: none;
  }
}
