/* The operator console's only stylesheet.
 *
 * A dense table somebody scans for one client, not a marketing page. Two colour
 * schemes, chosen by the operating system rather than by a toggle nobody would set:
 * every token below is redefined once under `prefers-color-scheme: dark`, and nothing
 * else in the file names a colour directly.
 */

:root {
  /* Neutrals with a deliberate cool cast rather than a pure grey. A console is read for
     hours; a neutral that leans very slightly toward the accent reads as chosen, and a
     pure #808080 reads as nobody having decided. */
  --ink: #14181f;
  --ink-quiet: #626d7d;
  --paper: #f3f5f9;
  --panel: #ffffff;
  --rule: #e2e6ed;
  --rule-soft: #edf0f4;
  --accent: #2358d8;
  /* The accent at the weight a background can carry. Used for the row you are on and
     nothing else, so "where am I" never competes with "what needs attention". */
  --accent-wash: #e8eefc;
  --alarm: #b42318;
  --caution: #a25a07;
  --good: #067647;
  --chip: #eaeef4;
  --radius: 7px;
  --radius-lg: 10px;
  /* One shadow, used sparingly. Panels lift off the paper by a hair; nothing floats. */
  --lift: 0 1px 2px rgb(16 24 40 / 6%), 0 1px 3px rgb(16 24 40 / 4%);
  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #e7eaf0;
    --ink-quiet: #939cab;
    --paper: #0f1216;
    --panel: #171b21;
    --rule: #272d36;
    --rule-soft: #1f242b;
    --accent: #7ea2ff;
    --accent-wash: #1b2436;
    --alarm: #ff8a7a;
    --caution: #e5a34a;
    --good: #5bd08e;
    --chip: #212832;
    --lift: none;
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.5;
  /* Grey text on a light ground is thin without this on macOS, and this console is
     mostly grey text. */
  -webkit-font-smoothing: antialiased;
}

code,
.mono {
  font-family: var(--mono);
  font-size: 0.88em;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* Keyboard focus, visible everywhere, and only for the keyboard. Without this the rail
   and every form control are navigable and invisible while being navigated. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ------------------------------------------------------------------ chrome */
.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 1.5rem;
  background: var(--panel);
  border-bottom: 1px solid var(--rule);
  /* Sticky rather than scrolling away. It is one line tall and it carries the only
     "sign out" in the console, which is the thing somebody looks for from wherever they
     have scrolled to. */
  position: sticky;
  top: 0;
  z-index: 20;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--ink);
  font-weight: 600;
}

.brand:hover {
  text-decoration: none;
}

.mark {
  display: inline-grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.wordmark {
  letter-spacing: -0.01em;
}

.wordmark em {
  color: var(--ink-quiet);
  font-style: normal;
  font-weight: 500;
}

.who {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.85rem;
  color: var(--ink-quiet);
}

/* The address is the identity; the role is a fact about it. A chip rather than more
   grey text, so "engineer" does not read as a second half of the email address. */
.who .role {
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  background: var(--chip);
  color: var(--ink-quiet);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.who form {
  margin: 0;
}

/* The rail and the page beside it. `align-items: start` is what lets the sidebar be
   sticky at all: a stretched flex item is already as tall as the row, so it has no room
   to move within and `position: sticky` silently does nothing. */
.frame {
  display: flex;
  align-items: start;
  gap: 2rem;
  /* One width for every screen in the console. This was 76rem here and widened to
     96rem for the integrations pages, so the sidebar moved sideways whenever you
     navigated into or out of them -- a rail that shifts under the cursor is a rail
     you stop trusting to stay put. The wider figure wins because the widest screen
     (a product page carrying its own second rail) has to fit inside it. */
  max-width: 88rem;
  margin: 0 auto;
  padding: 0 1.5rem;
}

.side {
  /* `top` clears the sticky header bar rather than the viewport edge, or the rail
     slides underneath it. */
  position: sticky;
  top: 3.75rem;
  flex: 0 0 12.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin: 1.75rem 0 2rem;
  padding: 0.85rem 0.7rem;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--lift);
}

.side-label {
  margin: 1.1rem 0.6rem 0.35rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule-soft);
  color: var(--ink-quiet);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* The account's own name sits in the second one, and a long name must not widen the
     rail and narrow every page behind it. */
  overflow-wrap: anywhere;
}

.side-label:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.side a {
  padding: 0.4rem 0.6rem;
  border-radius: var(--radius);
  color: var(--ink-quiet);
  font-size: 0.9rem;
  font-weight: 500;
  transition: background 0.12s ease, color 0.12s ease;
}

.side a:hover {
  background: var(--chip);
  color: var(--ink);
  text-decoration: none;
}

/* The page you are on, in the accent at the weight a background can carry. One marker,
   one colour, and nothing else in the rail competes with it. */
.side a.here {
  color: var(--accent);
  background: var(--accent-wash);
  font-weight: 600;
}

/* `min-width: 0` because a flex item's default minimum is its content, so one wide
   table -- a keyword list, a run timeline -- would push the whole page sideways instead
   of scrolling inside its own container. */
.content {
  flex: 1 1 auto;
  min-width: 0;
}

main {
  /* No width cap here: `.frame` above sets the page width once, and a second cap that
     applies to some screens and not others is what made the content column jump. Line
     length is held by the elements that carry prose (`max-width: 46rem` on paragraphs,
     asides and list items), which is where it belongs -- a table has no business being
     narrowed to the width of a sentence. */
  padding: 1.75rem 0 3rem;
}

footer {
  padding: 0 0 2rem;
  color: var(--ink-quiet);
  font-size: 0.8rem;
}

/* Under a narrow window the rail stops being a rail: sticky columns on a phone are how
   a screen ends up with no room for the thing somebody came to read. */
@media (max-width: 60rem) {
  .frame {
    flex-direction: column;
    gap: 0;
  }

  .side {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    flex-basis: auto;
    gap: 0.25rem;
    align-items: baseline;
    margin: 1rem 0 0;
    padding: 0.5rem 0.6rem;
  }

  .side-label {
    margin: 0 0.35rem 0 0;
    padding-top: 0;
    border-top: none;
  }
}

/* ------------------------------------------------------------------ blocks */
.heading {
  margin-bottom: 1.5rem;
}

/* The breadcrumb. An ordered list, because it is one -- and because a screen reader
   announcing "list of 4" tells somebody how deep they are before reading a word. The
   separator is CSS rather than a character in the markup, so it is never read aloud. */
.crumb {
  margin: 0 0 0.35rem;
  font-size: 0.82rem;
}

.crumb ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem;
  margin: 0;
  padding: 0;
}

.crumb li + li::before {
  content: "›";
  margin-right: 0.3rem;
  color: var(--ink-quiet);
}

/* Where you are: stated, not linked. Heavier than the trail behind it, because the
   last step is the answer to "where am I" and the rest is how you got here. */
.crumb [aria-current="page"] {
  color: var(--ink);
  font-weight: 600;
}

.crumb a {
  color: var(--ink-quiet);
}

.crumb a:hover {
  color: var(--accent);
}

h1 {
  margin: 0;
  font-size: 1.6rem;
  font-weight: 650;
  letter-spacing: -0.02em;
}

h2 {
  margin: 0 0 0.75rem;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-quiet);
}

.count {
  margin: 0.35rem 0 0;
  color: var(--ink-quiet);
  font-size: 0.875rem;
}

/* Prose inside a panel holds its own line length, now that `main` sets no width. A
   sentence running the full width of a 88rem page is one the eye loses its place in;
   a table at that width is a table you can finally read. So the cap lives on the text,
   not on the column. */
.panel > p,
.panel > ul,
.panel > ol,
.panel > blockquote {
  max-width: 46rem;
}

.panel {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--lift);
  padding: 1.15rem 1.3rem;
  margin-bottom: 1.25rem;
}

.panel.narrow {
  max-width: 26rem;
  margin: 3rem auto;
}

.columns {
  display: grid;
  grid-template-columns: minmax(14rem, 1fr) 2fr;
  gap: 1.25rem;
}

/* Two panels of the same standing, rather than the narrow-beside-wide the bare class
   is for. The run row and the engine's record are read against each other -- what we
   asked for on the left, what the engine did with it on the right -- and the 1fr/2fr
   split squeezed the left one until every artifact reference in it wrapped mid-token. */
.columns.even {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 46rem) {
  .columns {
    grid-template-columns: 1fr;
  }
}

/* ------------------------------------------------------------------ tables */
table {
  width: 100%;
  /* `separate` rather than `collapse`, and it is what makes a rounded table possible at
     all: collapsed borders are painted by the cells, so the corners of the table itself
     are square however much radius it is given. Every rule below is a cell's own
     bottom border, which collapses nothing and clips correctly. */
  border-collapse: separate;
  border-spacing: 0;
  font-size: 0.9rem;
}

th {
  text-align: left;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-quiet);
  font-weight: 700;
  padding: 0.6rem 0.75rem;
  background: var(--rule-soft);
  border-bottom: 1px solid var(--rule);
}

td {
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--rule-soft);
  vertical-align: top;
}

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

tbody tr:hover {
  background: var(--chip);
}

/* A card, not a grid of lines. The radius is put on the corner cells because the table
   itself cannot clip them. */
table.roster {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--lift);
  overflow: hidden;
}

table.roster th:first-child {
  border-top-left-radius: var(--radius-lg);
}

table.roster th:last-child {
  border-top-right-radius: var(--radius-lg);
}

/* The name is what somebody is scanning for; everything else on the row is a detail
   about it. Weight rather than colour, because the colour is already doing "this is a
   link". */
table.roster tbody td:first-child a {
  font-weight: 600;
}

.figure {
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

.figure.big {
  font-size: 1.6rem;
  text-align: left;
  margin: 0;
}

.overdrawn {
  color: var(--alarm);
}

.muted {
  color: var(--ink-quiet);
}

/* A term and its value, read down the left edge. The label column is sized in `ch` off
   the longest term these pages actually use ("Engine recoveries") rather than in `rem`:
   an 11rem column left the value with a third of a narrow panel, which is how a
   reference that is one long token came to be cut off at the card's edge. `max-content`
   capped at 40% sizes the column off the longest term ON THE PAGE, so "Status" does not
   reserve the room "Average cost per click" needs and neither of them wraps.
   `minmax(0,1fr)` on the value, because a grid track's default `min-content` floor is the
   width of the longest unbreakable word -- an artifact reference -- and that is what
   pushed the text past the border instead of wrapping it. */
dl {
  display: grid;
  grid-template-columns: minmax(min-content, max-content) minmax(0, 1fr);
  gap: 0 1rem;
  margin: 0;
  font-size: 0.9rem;
  align-items: baseline;
}

/* A hairline per row, so the eye tracks across a long list of terms without counting.
   On the pair, not on a row, because a grid has no row element to hang it from. */
dt,
dd {
  padding: 0.38rem 0;
  border-top: 1px solid var(--rule-soft);
}

dl > dt:first-of-type,
dl > dt:first-of-type + dd {
  border-top: 0;
  padding-top: 0;
}

dt {
  color: var(--ink-quiet);
  max-width: 14rem;
}

/* Values are references, ids and URLs: unbreakable tokens that a grid will happily run
   past the panel's border. Break them rather than clip them. */
dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.chip {
  display: inline-block;
  padding: 0.08rem 0.45rem;
  margin: 0 0.25rem 0.25rem 0;
  border-radius: 999px;
  background: var(--chip);
  font-size: 0.8rem;
  /* A chip is one label. Broken across two lines it stops reading as a badge and
     starts reading as a paragraph with a coloured background. */
  white-space: nowrap;
}

.chip.withdrawn {
  color: var(--alarm);
  text-decoration: line-through;
}

/* ------------------------------------------------------------------ forms */
label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-quiet);
}

/* A label names the box under it, so it sits ON ITS OWN LINE above that box.
   Inline by default, a label and its textarea sit side by side and the caption
   ends up beside the field it names rather than over it -- with the boxes
   staggered down the page as each one's height pushes the next along. */
.stack fieldset label,
.stack > label {
  display: block;
  margin-top: 0.85rem;
}

/* Every editable box the same size, whatever it holds. Sized here rather than
   by each field's `rows`, so keywords, headlines and descriptions line up in a
   column instead of stepping in and out by a row at a time. `block` because an
   inline-block textarea sits on the text baseline and carries a descender gap
   under it. */
.stack textarea {
  display: block;
  width: 100%;
  min-height: 8.5rem;
  box-sizing: border-box;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  font: inherit;
  /* Lists of short lines, read as a column: the proportional face of the rest
     of the console makes a keyword list look ragged. */
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 0.85rem;
  line-height: 1.45;
  /* `--panel`, not `--paper`: paper IS the page's own ground, so a box painted
     with it disappears into the page and reads as a disabled field rather than
     something to type in. Panel is the surface things sit ON, and it inverts
     correctly in dark mode where plain white would glare. */
  background: var(--panel);
  color: inherit;
  /* Only downward, and never narrower than the column: a textarea dragged wide
     would break the alignment this rule exists to create. */
  resize: vertical;
}

input {
  padding: 0.5rem 0.6rem;
  margin-bottom: 0.5rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  /* `--panel` for the same reason the textareas use it: paper is the page's own
     ground, so a field painted with it sinks into the page and reads as one
     that cannot be typed in. Every editable box in the console is panel. */
  background: var(--panel);
  color: var(--ink);
  font: inherit;
}

input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

button {
  padding: 0.5rem 0.95rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--accent);
  /* Literal white, not `--panel`: in dark mode the panel token is nearly black, and a
     button rendered dark-on-light-blue is the one control nobody can read. */
  color: #fff;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: filter 0.12s ease;
}

button:hover {
  filter: brightness(1.08);
}

button.quiet {
  background: transparent;
  border-color: var(--rule);
  color: var(--ink-quiet);
  font-weight: 500;
  padding: 0.25rem 0.6rem;
}

button.quiet:hover {
  filter: none;
  background: var(--chip);
  color: var(--ink);
}

/* ------------------------------------------------------------------ notes */
.refusal {
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius);
  border: 1px solid var(--alarm);
  color: var(--alarm);
  font-size: 0.88rem;
}

.empty {
  color: var(--ink-quiet);
  font-size: 0.9rem;
}

.aside {
  color: var(--ink-quiet);
  font-size: 0.8rem;
  max-width: 46rem;
}

/* ------------------------------------------------------------------ account tabs */
.tabs {
  display: flex;
  gap: 0.25rem;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--rule);
  /* Three steps fit a phone only by wrapping; the strip is navigation, so it
     stacks rather than scrolling out of reach. */
  flex-wrap: wrap;
}

/* `span` as well as `a`: a step that cannot be opened is not a link, and styling
   only the anchor made an unreachable step lose its shape entirely. */
.tabs a,
.tabs span {
  padding: 0.45rem 0.85rem;
  color: var(--ink-quiet);
  font-size: 0.88rem;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.tabs a.here,
.tabs span.here {
  color: var(--ink);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

/* A step whose page does not exist yet. Greyed and not a link -- an href here is
   one somebody clicks, landing on a page built from a digest nothing stored.
   `not-allowed` rather than `default`, so the pointer says so before the click. */
.tabs .unreachable {
  /* Mixed toward the ground rather than set to `--rule`: that token is a BORDER
     colour at 1.25:1 on the panel, which reads as a tab that failed to render
     rather than one that is waiting. This is ~2.4:1 -- plainly inactive beside a
     live tab's 5.25:1, and still legible, which a disabled control has to be for
     anyone to know what it will become. */
  color: color-mix(in srgb, var(--ink-quiet) 60%, var(--panel));
  cursor: not-allowed;
}

h2.section {
  margin: 0;
  font-size: 1.4rem;
  text-transform: none;
  letter-spacing: -0.01em;
  color: var(--ink);
}

/* ------------------------------------------------------------------ run states
 *
 * The three values of contracts.RunState, plus the two the platform record adds. Named
 * after the state itself so a template writes `chip {{ run.state }}` and a fourth state
 * arriving renders as a plain chip rather than as nothing at all.
 */
.chip.running,
.chip.live {
  background: color-mix(in srgb, var(--accent) 18%, var(--chip));
  color: var(--accent);
}

/* A resource the platform created and has not reversed. Deliberately NOT `.chip.live`:
   every gateway creates campaigns PAUSED, enabling is a separate refusable step, and
   that step writes no ledger row at all -- so a `platform_actions` row cannot tell a
   paused campaign from a spending one. Neutral, not green: "created" is a fact worth
   stating and not an outcome worth celebrating, and the green was what made a paused
   campaign read as one that is running. */
.chip.created {
  background: var(--chip);
  color: var(--ink-quiet);
}

/* Also used for a manifest declaring an agent in scope: a claim about what is meant to
   exist, which must not wear the colour of something that does.

   An approval that was granted. Deliberately NOT `.chip.live`, which this console uses
   for something that exists right now -- an engaged agent, an enabled user. It is NOT
   used for a ledger row: every gateway creates campaigns paused, and nothing records an
   enable, so `.chip.created` carries that instead. Permission to launch and a launched campaign are different
   facts, and giving them the same green is how a failed publication came to read as a
   running campaign on the campaigns screen. Green, because granted is the good outcome;
   quieter, because it is a decision and not a thing in the world. */
.chip.granted {
  background: color-mix(in srgb, var(--good) 14%, var(--chip));
  color: var(--good);
}

.chip.failed,
.chip.alarm {
  background: color-mix(in srgb, var(--alarm) 15%, var(--chip));
  color: var(--alarm);
}

/* A step that changed somebody else's advertising account. Deliberately NOT `.alarm`:
   these steps are what a successful launch is made of, and the red read as six rows of
   which two had gone wrong. Amber is the register this wants -- irreversible, worth
   finding at a glance, and not a failure. */
.chip.irreversible {
  background: color-mix(in srgb, var(--caution) 16%, var(--chip));
  color: var(--caution);
}

tr.failed td,
tr.undone td {
  background: color-mix(in srgb, var(--alarm) 7%, transparent);
}

tr.undone code {
  text-decoration: line-through;
  color: var(--ink-quiet);
}

/* ------------------------------------------------------------------ ad copy */
.ad {
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px dashed var(--rule);
}

.ad h3 {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  color: var(--ink-quiet);
  font-weight: 600;
}

.label {
  margin: 0.6rem 0 0.2rem;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-quiet);
}

ol.assets {
  margin: 0;
  padding-left: 1.4rem;
  font-size: 0.9rem;
}

/* A statement of fact rather than a failure -- the missing runtime store, an empty
 * result somebody should read as information. Deliberately not `.refusal`: an operator
 * who learns to read a red box as noise will read the real ones as noise too. */
.note {
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
  background: var(--panel);
  color: var(--ink-quiet);
  font-size: 0.88rem;
}

/* ------------------------------------------------------------------ writing
 *
 * Phase 13's half of the console. Everything below is about a button: whether it is
 * offered, whether it is disabled, and what it says it will reach.
 */

/* The cell that holds one row's buttons. Forms rather than links, everywhere, because
 * a GET that launches a campaign is a campaign launched by a link prefetcher -- so a
 * row's actions are several small forms and they have to sit beside each other. */
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
}

.actions form {
  display: flex;
  gap: 0.3rem;
  align-items: center;
  margin: 0;
}

/* A button that cannot work is shown, greyed, with the reason underneath -- never
 * hidden. An absent button reads as a feature that does not exist; a disabled one with
 * a sentence tells somebody which variable to set. */
button[disabled] {
  background: var(--rule);
  color: var(--ink-quiet);
  cursor: not-allowed;
}

select {
  padding: 0.35rem 0.4rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 0.85rem;
}

.small {
  font-size: 0.78rem;
  line-height: 1.35;
  max-width: 30rem;
}

/* The mode banner. It is a `.note` -- a statement of fact, not a failure -- with one
 * change: when this process really can reach Google, the border says so, because that
 * is the one state where a click spends a client's money (Q24). */
.mode.live {
  border-color: var(--alarm);
  color: var(--ink);
}

.mode strong {
  letter-spacing: 0.06em;
}

/* A run that is still being carried out somewhere else.
   Reads as information rather than as a warning: nothing is wrong, the work is simply
   happening in the worker instead of in the request that started it (the queues phase).
   Uses --accent, which is what `.chip.running` above is drawn from -- so the sentence
   and the state badge it explains are visibly the same colour. There is no --running
   token and inventing one here would have produced a notice that silently fell back to
   a hardcoded yellow matching nothing on the page. */
.notice {
  margin: 0 0 1rem;
  padding: 0.6rem 0.85rem;
  border-left: 3px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border-radius: 0 3px 3px 0;
  font-size: 0.92rem;
}

/* Onboarding forms, tucked behind a disclosure.
   Credential fields that sit open on a screen somebody leaves up all day are credential
   fields a shoulder or a screenshot catches, so the drawer is a small security decision
   rather than a layout one. */
.form-drawer {
  margin-top: 1rem;
  border-top: 1px solid var(--rule, rgba(128, 128, 128, 0.25));
  padding-top: 0.75rem;
}

.form-drawer > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.92rem;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  max-width: 34rem;
  margin-top: 0.75rem;
}

.stack label {
  font-size: 0.85rem;
  font-weight: 600;
  margin-top: 0.5rem;
}

.stack .hint {
  font-size: 0.82rem;
  margin: 0.15rem 0 0;
  opacity: 0.75;
}

.stack fieldset {
  border: 1px solid var(--rule, rgba(128, 128, 128, 0.25));
  border-radius: 4px;
  padding: 0.75rem 1rem 1rem;
  margin: 0 0 1rem;
}

.stack button {
  align-self: flex-start;
  margin-top: 1rem;
}

/* A form that was refused. Shares the alarm hue with the failed chip, because a person
   scanning the page should see the same colour mean the same thing. */
.refusal {
  margin: 0 0 1rem;
  padding: 0.6rem 0.85rem;
  border-left: 3px solid var(--alarm);
  background: color-mix(in srgb, var(--alarm) 10%, transparent);
  border-radius: 0 3px 3px 0;
}

/* A list that is a list of facts rather than a bulleted argument. Used where the marker
   would be noise -- contacts, platforms, anything one line long. */
ul.plain {
  margin: 0.5rem 0 1.25rem;
  padding: 0;
  list-style: none;
}

ul.plain li {
  padding: 0.3rem 0;
  border-bottom: 1px solid var(--rule);
}

ul.plain li:last-child {
  border-bottom: none;
}

/* ------------------------------------------------------------------ directories
 *
 * The two tables that span every account: sorted by clicking a header, filtered by a
 * select, paged at the bottom. Everything here is about making those three read as
 * controls rather than as decoration.
 */

/* A heading with one action beside it. The action sits with the thing it acts on rather
   than in the rail, which is for places you can go. */
.heading.with-action {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: 1rem;
}

/* A link that is a button. `a` rather than a form, because creating an account is a page
   you go to, and a POST that only navigates is a POST somebody cannot open in a tab. */
a.button {
  padding: 0.5rem 0.95rem;
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
}

a.button:hover {
  filter: brightness(1.08);
  text-decoration: none;
}

.filters {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1rem;
  padding: 0.75rem 0.9rem;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--lift);
  font-size: 0.9rem;
}

.filters label {
  color: var(--ink-quiet);
  font-weight: 600;
}

select {
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 0.9rem;
}

.filters .clear {
  color: var(--ink-quiet);
  font-size: 0.85rem;
}

/* A sortable header is the header, not a link inside it: the whole cell is the target,
   because a two-word label with a click area the width of the words is a control people
   miss. */
th a.sortable {
  display: block;
  margin: -0.6rem -0.75rem;
  padding: 0.6rem 0.75rem;
  color: inherit;
  font: inherit;
}

th a.sortable:hover {
  background: var(--chip);
  color: var(--ink);
  text-decoration: none;
}

/* Reserved whether or not this is the sorted column, so clicking a header does not shift
   every other one sideways. */
.marker {
  display: inline-block;
  width: 0.85em;
  margin-left: 0.15rem;
}

.pager {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin: 0.85rem 0 1.5rem;
  font-size: 0.85rem;
}

.pager .count {
  margin: 0;
}

.pages {
  display: flex;
  align-items: baseline;
  gap: 0.9rem;
}

/* The second banner: what wrote the copy, as opposed to what a launch reaches. Quieter
   than `.mode` because it is never an alarm -- a console standing in for the model is
   doing what somebody asked it to -- but never invisible, because synthetic ad copy is
   the one thing on this console that looks exactly like the real thing. */
.note.standin {
  border-left: 3px solid var(--ink-quiet);
  background: var(--chip);
  color: var(--ink-quiet);
}

.note.standin strong {
  color: var(--ink);
  letter-spacing: 0.04em;
}

/* ------------------------------------------------------- integrations
 *
 * A product rail beside the content, the same shape as the console's own side rail but
 * scoped to one platform. It reuses `.tabs` for the section strip rather than defining a
 * second one: two tab strips that look almost alike is two of them to keep in step, and
 * the one that drifts is the one nobody is looking at.
 */
.split {
  display: grid;
  /* `minmax(0, 1fr)` on the content column rather than `1fr`: a grid track sized to its
     content refuses to shrink below a wide table's natural width, and the table then
     pushes the whole page sideways instead of scrolling inside its own wrapper. */
  grid-template-columns: 13rem minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
}

/* The section strip on a product page. `.tabs` wraps its labels onto two lines at this
   width -- these labels are sentences ("Rules the API only reveals on launch"), not the
   single words the account tabs carry -- so it scrolls sideways instead. A tab strip two
   lines tall pushes the content it labels below the fold. */
.pane > .tabs {
  overflow-x: auto;
  flex-wrap: nowrap;
}

.pane > .tabs a {
  white-space: nowrap;
}

/* A step table and a rules table are wide by nature: five columns of prose. They scroll
   inside their own wrapper rather than widening the page, so the rail and the strip stay
   where they are. A wrapper rather than `display: block` on the table itself, which would
   drop the column alignment that makes a table worth using. */
.scrolls {
  overflow-x: auto;
  max-width: 100%;
}

/* The product rail. Sticky for the same reason the console's rail is: a reader comparing
   two products should not have to scroll a rules table back up to reach the next one. */
/* Standing, as a dot rather than a word, because the word is in the heading and on the
   chip already. Three colours for three states -- a blocked product is waiting on
   somebody and a closed one is waiting on nobody, and those need telling apart at a
   glance (`core/integrations.Standing`). */
.dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex: 0 0 auto;
  transform: translateY(-1px);
  background: var(--ink-quiet);
}

.dot.supported {
  background: var(--good);
}

.dot.blocked {
  background: color-mix(in srgb, var(--alarm) 60%, var(--ink-quiet));
}

.dot.closed {
  background: var(--ink-quiet);
}

.chip.supported {
  background: color-mix(in srgb, var(--good) 15%, var(--chip));
  color: var(--good);
}

.chip.blocked {
  background: color-mix(in srgb, var(--alarm) 12%, var(--chip));
  color: var(--alarm);
}

.lede {
  color: var(--ink-quiet);
  max-width: 46rem;
  margin: 0.75rem 0 1.25rem;
}

.bullets {
  margin: 0;
  padding-left: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.bullets li {
  max-width: 46rem;
}

/* The counted facts behind a product's score. A row of them, wrapping, rather than a
   table: there is no relationship between the columns and a table would imply one. */
.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 1.25rem;
}

.metric {
  background: var(--panel);
  padding: 0.6rem 0.8rem;
}

.metric .k {
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-quiet);
}

.metric .v {
  font-family: var(--mono);
  font-size: 1.15rem;
  margin-top: 0.15rem;
  font-variant-numeric: tabular-nums;
}

.surfaces {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}

.surface {
  background: var(--panel);
  padding: 0.6rem 0.75rem;
}

.sname {
  font-weight: 600;
  font-size: 0.9rem;
}

.sdesc {
  font-size: 0.85rem;
  color: var(--ink-quiet);
  margin-top: 0.15rem;
}

/* How a person meets the ad, in order. Numbered by CSS rather than by markup so the
   steps stay plain strings in `core/google_products.py`. */
ol.flow {
  counter-reset: flowstep;
  list-style: none;
  padding: 0;
  margin: 0;
}

ol.flow li {
  counter-increment: flowstep;
  position: relative;
  padding: 0.5rem 0 0.5rem 2rem;
  border-bottom: 1px solid var(--rule-soft);
  max-width: 46rem;
  color: var(--ink-quiet);
}

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

ol.flow li::before {
  content: counter(flowstep);
  position: absolute;
  left: 0;
  top: 0.5rem;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 50%;
  background: var(--accent-wash);
  color: var(--accent);
  font-family: var(--mono);
  font-size: 0.72rem;
  display: grid;
  place-items: center;
}

.verdict {
  border-left: 3px solid var(--accent);
  background: var(--accent-wash);
  padding: 0.6rem 0.85rem;
  border-radius: 0 var(--radius) var(--radius) 0;
  max-width: 46rem;
}

/* Prose transcribed from a written assessment, carrying its own headings and tables. It
   is the only place in this console that renders markup from a constant, and it is a
   constant in this repository -- never anything a request supplied. */
.prose h3 {
  font-size: 1rem;
  margin: 1.1rem 0 0.4rem;
}

.prose h3:first-child {
  margin-top: 0;
}

.prose p,
.prose li {
  max-width: 46rem;
}

/* The transcribed prose wraps its own tables in `.wrap`; same job as `.scrolls`. */
.prose .wrap {
  overflow-x: auto;
  max-width: 100%;
}

.prose .gate {
  border-left: 3px solid var(--alarm);
  background: color-mix(in srgb, var(--alarm) 7%, transparent);
  padding: 0.6rem 0.85rem;
  border-radius: 0 var(--radius) var(--radius) 0;
  max-width: 46rem;
}

@media (max-width: 60rem) {
  .split {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Prose somebody typed, quoted back. Set apart from the console's own sentences
   because it is the one piece of text on the page written by a person rather than
   by this application -- and it is what a campaign is recognised by. */
.blurb {
  margin: 0;
  padding: 0.6rem 0 0.6rem 0.9rem;
  border-left: 3px solid var(--rule);
  color: var(--ink);
  max-width: 46rem;
  white-space: pre-wrap;
}

/* A rebuilt description, visibly not a quotation. Dashed, because the solid rule beside
   `.blurb` marks words a person actually wrote and these are not those. */
.blurb.rebuilt {
  border-left-style: dashed;
  color: var(--ink-quiet);
  font-style: italic;
}

/* A read-back fault: one line saying which field differs, with the full diff the
   platform's own words behind it. Collapsed, because the evidence is a thousand
   characters of two near-identical reprs and the summary is what gets read. */
.fault summary {
  cursor: pointer;
}

.fault pre {
  white-space: pre-wrap;
  word-break: break-word;
  margin: 0.3rem 0 0;
  padding: 0.5rem;
  background: var(--rule-soft);
  border-radius: var(--radius);
  max-height: 16rem;
  overflow: auto;
}

/* A count beside a heading. Quieter than the heading it qualifies: it says how much is
   below, which matters, but the heading says what it is, which matters more. */
.count-of {
  font-weight: 400;
  font-size: 0.82rem;
  color: var(--ink-quiet);
  margin-left: 0.4rem;
}

/* A rail entry one level under the section above it. Indented rather than given its own
   label, because these ARE that section -- the platforms of Integrations, the way an
   account's tabs are that account's. */
.side a.nested {
  padding-left: 1.7rem;
  font-size: 0.88rem;
}

.side a.nested-2 {
  padding-left: 2.6rem;
  font-size: 0.86rem;
}

/* The sections of the product you are in -- the last level the breadcrumb names, so the
   last the rail shows. Quieter again: by this depth the rail is orienting you, not
   offering the main move. */
.side a.nested-3 {
  padding-left: 3.4rem;
  font-size: 0.82rem;
  color: var(--ink-quiet);
}

/* One ad group or asset group, as the plan describes it. Boxed so the copy inside it is
   read as belonging to this group and not the next one. */
.group {
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem;
  margin-bottom: 0.75rem;
}

.group h4 {
  margin: 0 0 0.4rem;
  font-size: 0.95rem;
}

.group p {
  margin: 0.35rem 0;
}

/* Ad copy: each headline and description on its own line, because that is how Google
   treats them -- separate assets it assembles, not a paragraph somebody wrote. */
ul.copy {
  margin: 0.2rem 0 0.5rem;
  padding-left: 1.1rem;
  font-size: 0.9rem;
}

/* The creative itself, on the page. A campaign is its artwork as much as its copy, and
   a line of text naming an image ("landscape 1200x628 stand-in") describes a rectangle
   rather than showing one. A stand-in is generated from the same spec the gateway sent,
   so what is shown is the image in the account. */
.artwork {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0.4rem 0 0.75rem;
}

.artwork figure {
  margin: 0;
  max-width: 15rem;
  border: 1px solid var(--rule-soft);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--rule-soft);
}

.artwork img {
  display: block;
  width: 100%;
  height: auto;
  /* The plan's own ratio is in the caption; this keeps a tall logo from dominating a
     row of landscape images. */
  max-height: 10rem;
  object-fit: contain;
  background: var(--panel);
}

/* Stand-in artwork is a solid grey PNG. Dashed, so a reader does not take a flat
   rectangle for a photograph the client actually supplied. */
.artwork figure.standin {
  border-style: dashed;
}

.artwork .missing {
  padding: 2rem 0.5rem;
  text-align: center;
  color: var(--ink-quiet);
  font-size: 0.8rem;
}

.artwork figcaption {
  padding: 0.4rem 0.5rem;
  background: var(--panel);
  font-size: 0.78rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  align-items: baseline;
}

/* A tick, a cross, or an em dash for "this does not exist to have". Colour is not
   the only signal -- the glyphs differ -- so the column still reads without it. */
td.access {
  text-align: center;
  font-size: 1rem;
}
td.access.yes { color: var(--good, #1a7f37); }
td.access.no { color: var(--muted-strong, #8a8f98); }
td.access.none { color: var(--muted, #b1b5bd); }

/* One account's wiring: the ad account, who we act as, and which client. Stacked
   rather than a nested table -- a platform usually has one or two. */
.wiring { line-height: 1.5; }
.wiring + .wiring { margin-top: 0.35rem; }

/* Choosing what kind of campaign to draft. Each option is a paragraph of real
   explanation, not a one-word label, because the choice decides what question the
   model is asked and the differences between products are the whole subject.

   This used to borrow `.products`, a sticky sidebar rule written for a narrow
   column of LINKS. A full-width fieldset in it came out squeezed to a third of
   the page and pinned, so the text below scrolled underneath and overlapped it. */
.choices {
  display: grid;
  gap: 0.5rem;
  margin: 0 0 1.25rem;
  padding: 0;
  border: 0;
}

.choices legend {
  padding: 0;
  margin-bottom: 0.6rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-quiet);
}

/* The whole option is the target, not just the radio: these are paragraphs, and
   a reader who has finished one should not have to travel back to a 13px circle. */
.choice {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.15rem 0.6rem;
  margin: 0;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
}

.choice:hover {
  border-color: var(--rule-strong, var(--ink-quiet));
}

/* The chosen one, said with a border rather than a wash: the text inside is long
   enough that a tinted background would fight it for attention. */
.choice:has(input:checked) {
  border-color: var(--accent);
  box-shadow: inset 2px 0 0 var(--accent);
}

.choice:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.choice label {
  display: contents;
  cursor: pointer;
}

.choice input[type="radio"] {
  margin: 0.2rem 0 0;
}

.choice strong {
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

/* What the product actually is. Sits under the name, in the second column, so a
   scan down the page reads the names alone. */
.choice .teaches {
  grid-column: 2;
  color: var(--ink-quiet);
  font-size: 0.9rem;
  line-height: 1.5;
}

/* A form whose fields are PROSE takes the width prose takes here (46rem, the cap
   `.panel > p` uses) rather than `.stack`'s 34rem, which is sized for a column of
   short inputs. The cap has to lift on the form: `.stack` bounds the whole
   column, so widening only the fieldset inside it changes nothing.

   The textarea below the chooser wants the room too -- somebody is writing a
   paragraph into it, and 34rem is a narrow trench to write one in. */
form.stack.wide {
  max-width: 46rem;
}

/* A form that calls a model or a platform takes seconds. Without a sign that
   anything is happening, the button reads as one that did not work and gets
   pressed again -- a second draft, or a second campaign.

   The page dims rather than showing a spinner: what is waiting is the whole
   page, not one corner of it, and dimming says "not yet" about every control at
   once instead of only the one that was pressed. */
/* Keyed off the FORM that is submitting, not off <html> or <body>. A flag on a
   long-lived element has to be cleared at the right moment on the next page,
   and a browser restoring form state re-fires submit after that moment has
   passed -- leaving `pointer-events: none` on a page that is not submitting
   anything, unclickable with nothing on screen to say why. A new document
   brings new forms, so this cannot outlive the page that set it. */
form[data-busy="yes"] {
  opacity: 0.55;
  transition: opacity 0.15s ease;
  pointer-events: none;
}

/* The pressed button stays at full strength -- it is the thing still working,
   and dimming it too would leave nothing on the page to look at. */
form[data-busy="yes"] button[aria-busy="true"] {
  opacity: 1;
  cursor: progress;
}

/* Said with the cursor and the text, not with colour alone. */
button[aria-busy="true"] {
  cursor: progress;
}

/* Somebody who has asked for less motion gets the dim without the fade. */
@media (prefers-reduced-motion: reduce) {
  form[data-busy="yes"] {
    transition: none;
  }
}
