/* The viewer's tokens and appearance: the `:root` vocabulary and every rule
   that paints.

   This is the half of the old `viewer/viewer.css` a replacement renderer is
   expected to honour. `render.js` writes no colour of its own — it names
   these tokens through `var()` on the elements it builds — so a restyle is an
   edit to the token sets below rather than a sweep through a renderer, and
   a page built on a different drawing still looks like this one.
   `docs/dev/viewer-renderer-seam.md` states that expectation.

   The look is the Workbench: dark first, with a light theme beside it. The
   dark set is on `:root` and is what a page opens in. The light set is
   written twice, because a media query and an attribute cannot share a
   rule: once for a browser that prefers light, standing aside wherever the
   root element carries `data-theme="dark"` — a reader who chose dark, or a
   page that opens dark by its own markup, as `edit.html` and `gallery.html`
   do — and once for a reader who chose light with the page's theme control,
   which `theme.js` writes as `data-theme` on the root element for as long as
   the page is open. The three sets declare the same tokens,
   and the two light ones the same values;
   `crates/flow-cli/tests/it/viewer_assets/colour_tokens.rs` holds them to
   both.

   Structure and sizes live in `viewer/shell.css`, which carries no colour at
   all — the three type sizes among them. */

:root {
  color-scheme: dark;

  /* The surfaces: the panels a page is built of, the canvas recessed below
     them that the cards stand on, and the cards themselves. The canvas and
     the panels are two values so that a card stands on something rather
     than on the page. */
  --panel: #1c1f25;
  --canvas: #121418;
  --card: #262a32;
  --ink: #e4e7eb;
  --select: #e8c46a;

  /* Inside a card: the identity band across its top, a shade off the card
     so the id and its kind read as the card's heading; the hairline the
     `in` and `out` sides are ruled off by and a chip is outlined with,
     quieter than `--line` because it divides a card rather than joining
     two; and the inset a chip's word stands on. */
  --band: #2f343d;
  --hairline: #3b414b;
  --chip: #1d2026;

  /* The structural vocabulary: what a construct *is*. A node's kind rides
     the rail down its card's left edge in `--step` or `--workflow`, and the
     card's outline stays `--line` whatever its kind, which leaves hue free
     for a run, a diagnostic or a selection to speak with. */
  --line: #5d6571;
  --step: #5aa2e0;
  --workflow: #b08ae0;
  --gate: #e39a4f;
  --guardrail: #e26d8a;
  --muted: #9aa2ad;

  /* A card's sockets: a binding's dot, filled, where the artifact of its row
     leaves or arrives, and the header's two ports, open rings, where an edge
     that hands no artifact over does. Two tokens, so the two read as two
     things: an artifact and a connection. */
  --socket: #5aa2e0;
  --port: #9aa2ad;

  /* Run state: what a run is *doing* over that structure. These are tokens
     of run state's own rather than the structural ones borrowed, because a
     borrowed token makes restyling a gate silently restyle "you are here" —
     and the two say different kinds of thing about the same drawing. */
  --run-member: #2cc5b0;
  --run-current: #f58b4c;
  --run-visited: #52c77a;
  --run-gate: #f06b6b;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;
    --panel: #f5f6f8;
    --canvas: #e7eaee;
    --card: #ffffff;
    --ink: #1c2027;
    --select: #b07d12;
    --band: #eef1f4;
    --hairline: #d3d8de;
    --chip: #f3f5f7;
    --line: #8a8f98;
    --step: #2f6f9f;
    --workflow: #7a4fa3;
    --gate: #a35a1f;
    --guardrail: #9f2f4f;
    --muted: #5f6670;
    --socket: #2f6f9f;
    --port: #5f6670;
    --run-member: #0f766e;
    --run-current: #c2410c;
    --run-visited: #15803d;
    --run-gate: #b91c1c;
  }
}

:root[data-theme="light"] {
  color-scheme: light;
  --panel: #f5f6f8;
  --canvas: #e7eaee;
  --card: #ffffff;
  --ink: #1c2027;
  --select: #b07d12;
  --band: #eef1f4;
  --hairline: #d3d8de;
  --chip: #f3f5f7;
  --line: #8a8f98;
  --step: #2f6f9f;
  --workflow: #7a4fa3;
  --gate: #a35a1f;
  --guardrail: #9f2f4f;
  --muted: #5f6670;
  --socket: #2f6f9f;
  --port: #5f6670;
  --run-member: #0f766e;
  --run-current: #c2410c;
  --run-visited: #15803d;
  --run-gate: #b91c1c;
}

body {
  background: var(--panel);
  color: var(--ink);
}
h2 {
  color: var(--muted);
}
p.lede,
p.hint {
  color: var(--muted);
}
.status {
  color: var(--muted);
}
.status[data-state="ready"] {
  color: var(--step);
}
.status[data-state="failed"] {
  color: var(--guardrail);
}

/* A drawing that is no longer a picture of the document it was made from:
   the refresh that would have replaced it did not project, and what is on
   screen is the last one that did. It is dimmed rather than removed —
   blanking the canvas on every half-written save is what following exists
   to avoid — and the status line beside it says so in words, because a
   reader should never have to work out how stale from how grey. */
.status[data-state="stale"] {
  color: var(--gate);
}
#graph[data-stale="true"] {
  opacity: 0.55;
}
#diagnostics li,
#overlay-diagnostics li {
  border-left-color: var(--line);
}
#diagnostics li[data-severity="error"] {
  border-left-color: var(--guardrail);
}
#diagnostics li[data-severity="warning"] {
  border-left-color: var(--gate);
}

/* A state document binding to no bundle member is refused whole, and the
   graph shows no part of that run. The panel marks that entry and the colour
   is decided here, where every other one is. */
#overlay-diagnostics li[data-stale="true"] {
  border-left-color: var(--guardrail);
}
/* The trail back out of the doors the canvas was opened through. A door is
   drawn `--workflow` on the canvas, so the way back through one is too: the
   trail and the control it leads from say the same thing about the same
   member. The separator is punctuation and carries the line's own colour,
   and the member standing on the canvas is left in the page's text colour —
   it is where you are, not somewhere to go. */
.breadcrumb-back {
  background: none;
  color: var(--workflow);
}
.breadcrumb-separator {
  color: var(--line);
}
.member {
  border-color: var(--line);
}
.member dt {
  color: var(--muted);
}
.scope-guardrails {
  color: var(--guardrail);
}

/* The recessed canvas: the drawing inside a member, and the editor's canvas
   region, paint with `--canvas` rather than the panels' `--panel`, so the
   cards on it stand on a surface of their own. */
.member svg,
.editor-canvas {
  background: var(--canvas);
}
svg .node-box {
  fill: var(--card);
  stroke: var(--line);
}
svg .node-kind {
  fill: var(--muted);
}
svg .guardrail {
  fill: var(--guardrail);
}
svg .edge {
  fill: none;
  stroke: var(--line);
}
/* A binding's dot sits on the card's border, ringed in the card's own
   surface so the border reads as passing behind it rather than through. A
   header port is an open ring on the border, filled with the card, so an
   edge ending there reads as plugged in rather than as landing on a dot. */
svg .port-dot {
  fill: var(--socket);
  stroke: var(--card);
}
svg .port-head {
  fill: var(--card);
  stroke: var(--port);
}
/* A data wire is an artifact's way to a step no edge carries it to: dotted
   in `--socket`, the colour of the row dots it runs between, so it reads as
   the artifact travelling rather than as one more edge in `--line`. */
svg .data-wire {
  fill: none;
  stroke: var(--socket);
}
/* A workflow card is a container: the cards stacked behind it are its own
   surface outlined in `--workflow`, the hue its rail carries, the farther one
   fainter. Its header's open control is inset on the chip surface with its
   chevron in `--workflow`, lifts onto the band while pointed at or focused,
   and is muted where the door opens onto nothing. */
svg .door-stack-card {
  fill: var(--card);
  stroke: var(--workflow);
  stroke-opacity: 0.85;
}
svg .door-stack-card:first-child {
  stroke-opacity: 0.5;
}
svg .door-open-body {
  fill: var(--chip);
  stroke: var(--workflow);
}
svg .door-open-glyph {
  fill: none;
  stroke: var(--workflow);
}
svg .door-open:not([aria-disabled="true"]):hover .door-open-body,
svg .door-open:focus-visible .door-open-body {
  fill: var(--band);
}
svg .door-open[aria-disabled="true"] .door-open-body {
  stroke: var(--hairline);
}
svg .door-open[aria-disabled="true"] .door-open-glyph {
  stroke: var(--muted);
}

/* The ghost controls of the editor's rail and inspector, whose sizes are
   `shell.css`'s: a button is its label on the panel itself, ruled off in a
   hairline, and lifts onto the band a card's heading stands on while it is
   pointed at; a disabled one is muted. A field or a choice is inset on the
   chip surface a card's chips stand on. A value edited in place reads as
   the value it is — no box at all — until it is pointed at or edited, and an
   icon control is its glyph alone until then. A section's heading, a rail
   entry and a guardrail's row are rows rather than buttons, muted or in the
   ink, lifting onto the band the same way. */
:where(.editor-rail, .editor-inspector) button {
  background: none;
  color: var(--ink);
  border-color: var(--hairline);
}
:where(.editor-rail, .editor-inspector) button:hover:not(:disabled),
.section-toggle:hover,
.rail-entry:hover,
.guardrail-row:hover {
  background: var(--band);
  border-color: var(--line);
}
:where(.editor-rail, .editor-inspector) button:disabled {
  color: var(--muted);
}
:where(.editor-rail, .editor-inspector) :where(input:not([type="checkbox"]), select, textarea) {
  background: var(--chip);
  color: var(--ink);
  border-color: var(--hairline);
}
.inspector-value:not(:hover, :focus),
.edit-icon:not(:hover, :focus) {
  background: none;
  border-color: var(--panel);
}
.inspector-value:focus,
:where(.editor-rail, .editor-inspector) :where(input, select, textarea):focus {
  border-color: var(--step);
}
.section-toggle {
  background: none;
  color: var(--muted);
}
.rail-entry[aria-current="true"] {
  background: var(--band);
}
/* The palette's tiles, which `edit-chrome.js` marks with the kind each adds
   and never paints. A tile is one of the rail's ghost controls, ruled down
   its near edge and its icon stroked in the colour of what it adds, as the
   card it adds is railed; an artifact, which no card is, in the ink on the
   line's rule. A disabled tile is muted whole, icon and rule included, and
   lifts onto nothing when pointed at. */
.palette-icon {
  fill: none;
}
.editor-palette .palette-tile[data-tile="step"] {
  border-left-color: var(--step);
}
.editor-palette .palette-tile[data-tile="workflow"] {
  border-left-color: var(--workflow);
}
.editor-palette .palette-tile[data-tile="gate"] {
  border-left-color: var(--gate);
}
.editor-palette .palette-tile[data-tile="guardrail"] {
  border-left-color: var(--guardrail);
}
.editor-palette .palette-tile[data-tile="artifact"] {
  border-left-color: var(--line);
}
.palette-tile[data-tile="step"] .palette-icon {
  stroke: var(--step);
}
.palette-tile[data-tile="workflow"] .palette-icon {
  stroke: var(--workflow);
}
.palette-tile[data-tile="gate"] .palette-icon {
  stroke: var(--gate);
}
.palette-tile[data-tile="guardrail"] .palette-icon {
  stroke: var(--guardrail);
}
.palette-tile[data-tile="artifact"] .palette-icon {
  stroke: var(--ink);
}
.editor-palette .palette-tile[aria-disabled="true"] {
  color: var(--muted);
  border-left-color: var(--hairline);
}
.editor-palette .palette-tile[aria-disabled="true"]:hover {
  background: none;
  border-color: var(--hairline);
}
.editor-palette .palette-tile[aria-disabled="true"] .palette-icon {
  stroke: var(--muted);
}
.inspector-fields dt,
.gate-note,
.skill-note,
.artifact-note,
.guardrail-note,
.guardrail-statement,
.guardrail-verdict,
.condition-note,
.condition-reason,
.edit-action-reason,
.palette-reason,
.skill-detail,
.skill-source,
.gate-argv-row::marker {
  color: var(--muted);
}

/* The bar's controls, the canvas corner's fit and 100%, and the editor's
   density switch and legend control are the same ghost controls, sized in
   `shell.css` and `edit.css`; the legend control keeps the band while the
   legend is open. The link to the other page is one of them, in the ink rather
   than in the browser's link colours. The view controls stand over the
   drawing, so they are on the panel rather than on nothing. The validity
   chip says its state in colour as well as in its words: `valid` in
   `--step`, as a ready status line is, and a count of diagnostics in
   `--guardrail`, as an error in the strip is. Save, the page's primary
   action, is the one filled control, in `--step` with its word on the card
   surface, while there is something to save; with nothing unsaved it is a
   disabled ghost control like any other. */
:where(.editor-bar, .editor-session) > button,
:where(.editor-bar) > .page-link,
.canvas-view-control,
.editor-status .density-control,
.editor-status .legend-toggle {
  background: none;
  color: var(--ink);
  border-color: var(--hairline);
}
.canvas-view-control {
  background: var(--panel);
}
/* The back control out of a nested member is one of those ghost controls
   on the panel, its words in `--workflow`: it is the way back through a
   door, as a breadcrumb entry is, and says so in the same colour. */
.canvas-back {
  background: var(--panel);
  color: var(--workflow);
  border-color: var(--hairline);
}
:where(.editor-bar, .editor-session) > button:hover:not(:disabled),
:where(.editor-bar) > .page-link:hover,
.canvas-view-control:hover,
.canvas-back:hover,
.editor-status .density-control:hover,
.editor-status .legend-toggle:hover,
.editor-status .legend-toggle[aria-expanded="true"] {
  background: var(--band);
  border-color: var(--line);
}
/* While the data wires are all shown, their switch says so in `--socket`,
   the colour the dotted wires and the row dots they join are drawn in. */
.density .wires-control[aria-pressed="true"] {
  color: var(--socket);
  border-color: var(--socket);
}
/* The legend a reader asked for stands over the drawing on the panel, ruled
   off from it in the hairline a chip is outlined with. */
.editor-legend {
  background: var(--panel);
  border-color: var(--hairline);
}
:where(.editor-bar, .editor-session) > button:disabled {
  color: var(--muted);
}
/* The editor's group of icons after Save — Compile, the theme toggle, the
   gallery link and Quit — are quieter still than the ghost controls: no
   rule of their own until pointed at, each icon stroked in `--muted` and
   lifted into the ink with the band behind it. Compile keeps the band while
   the drawer stands open on the compile panel. */
.bar-tools > :is(button, a) {
  background: none;
  border-color: var(--panel);
}
.bar-icon {
  fill: none;
  stroke: var(--muted);
}
.bar-tools > :is(button, a):hover:not(:disabled),
.bar-tools > [aria-expanded="true"] {
  background: var(--band);
  border-color: var(--hairline);
}
.bar-tools > :is(button, a):hover:not(:disabled) .bar-icon,
.bar-tools > [aria-expanded="true"] .bar-icon {
  stroke: var(--ink);
}
.bar-tools > :disabled .bar-icon {
  stroke: var(--hairline);
}
#validity[data-valid="true"] {
  color: var(--step);
  border-color: var(--step);
}
#validity[data-valid="false"] {
  color: var(--guardrail);
  border-color: var(--guardrail);
}
.session-save:not(:disabled),
.session-save:not(:disabled):hover {
  background: var(--step);
  color: var(--card);
  border-color: var(--step);
}
.session-save:not(:disabled):hover {
  border-color: var(--ink);
}

/* The skill picker's dropdown stands over the inspector on the card surface,
   the entry the arrow keys walked to lifted onto the band. */
.skill-list {
  background: var(--card);
  border-color: var(--hairline);
}
.skill-entry[aria-selected="true"],
.skill-entry:hover {
  background: var(--band);
}

/* The condition builder's verdicts on a fan-out, which `edit-conditions.js`
   marks and never paints. A rejection is drawn down its edge in
   `--guardrail`, as an error in the strip is; a fan-out that is not total
   says so in `--gate`, the colour of what decides a transition, and one
   that is total in `--step`, as a ready status line does. */
.condition-state {
  border-left-color: var(--guardrail);
}
.condition-totality[data-total="false"] {
  color: var(--gate);
}
.condition-totality[data-total="true"] {
  color: var(--step);
}

/* The binding screens. An artifact is bound from its `out`-row dot, painted
   with the card's sockets above; the statement `edit-artifacts.js` makes of
   an artifact a path has made typed is in `--step`, since a typed artifact is
   one `flow step done` checks. */
.artifact-typed {
  color: var(--step);
}
/* A step's `+ input` and `+ output` are ghost controls ruled on the line, and
   one marked disabled is muted on the hairline and lifts onto nothing when
   pointed at, as a disabled tile does. The choice one opens stands on the
   chip surface in a hairline, each artifact a row lifting onto the band
   while it is pointed at or stood on, its type muted. */
.binding-add {
  border-color: var(--line);
}
.binding-add[aria-disabled="true"],
.binding-add[aria-disabled="true"]:hover {
  background: none;
  color: var(--muted);
  border-color: var(--hairline);
}
.binding-choices {
  background: var(--chip);
  border-color: var(--hairline);
}
.binding-choice {
  background: none;
}
.binding-choice:hover,
.binding-choice:focus {
  background: var(--band);
}
.binding-choice-type {
  color: var(--muted);
}

/* Remove node, which ends a node's inspector, is destructive: its word in
   `--guardrail`, as an error in the strip is, on nothing even while pointed
   at, and ruled off from the sections above it in the hairline. Disabled,
   it is muted as every disabled control is. */
.inspector-end {
  border-top-color: var(--hairline);
}
.inspector-remove-control,
.inspector-remove-control:hover:not(:disabled) {
  background: none;
  color: var(--guardrail);
}
.editor-inspector .inspector-remove-control:disabled {
  color: var(--muted);
}

/* The Gate section, which `edit-gates.js` marks and never paints. Where a
   validator's command runs is said in `--gate`, the colour of what decides a
   transition; a Test's exit that would let a run through is in `--step`, as
   a ready status line is, and one that would hold it back in `--guardrail`,
   as an error in the strip is; the mark on a tail the host cut is
   `--muted`, like the other asides a drawing makes. */
.gate-statement {
  color: var(--gate);
}
.gate-exit[data-passed="true"] {
  color: var(--step);
}
.gate-exit[data-passed="false"] {
  color: var(--guardrail);
}
.gate-cut {
  color: var(--muted);
}

/* The missing-skill marker, which `edit-skills.js` states in the inspector
   and `editor.js` draws on the card of a step naming a skill this machine
   does not have. It is not a diagnostic, so it is never painted as an error
   is: it is `--gate`, as a fan-out that is not total is — something to act
   on, which blocks nothing — standing on the card surface. */
.skill-missing {
  color: var(--gate);
  border-color: var(--gate);
}
svg .node-marker circle {
  fill: var(--card);
  stroke: var(--gate);
}
svg .node-marker-glyph {
  fill: var(--gate);
}

/* The editor's selection, which `editor.js` marks on the group standing for
   a node or an edge. Like the overlay's marks below, it is painted here,
   where the rule can reach the shape inside the group. */
svg g[data-selected="true"] .node-box,
svg g[data-selected="true"] .edge {
  stroke: var(--select);
}

/* A palette tile carried onto the canvas, which `edit-palette.js` draws and
   never paints. Its ghost is the card it adds on the card surface, outlined
   dashed and railed in the tile's kind as the tile is — an artifact, which
   no card is, on the line's rule — and seen a little through, so what it
   would land on shows under it. The one line saying why a target refuses it
   stands on the panel, ruled in `--guardrail`, as an error in the strip is.
   What would take the tile is marked in `--select`, as a selection is: a
   card's outline and a line drawn heavier, the canvas itself outlined
   dashed inside its edge, and the rail's ARTIFACTS outlined dashed round
   its list. A member with no node says what goes there in
   `--muted`, as the drawing's other asides are. */
.drag-ghost {
  opacity: 0.7;
}
.drag-ghost-box {
  fill: var(--card);
  stroke: var(--line);
  stroke-dasharray: 5 3;
}
.drag-ghost[data-tile="step"] .drag-ghost-rail {
  fill: var(--step);
}
.drag-ghost[data-tile="workflow"] .drag-ghost-rail {
  fill: var(--workflow);
}
.drag-ghost[data-tile="gate"] .drag-ghost-rail {
  fill: var(--gate);
}
.drag-ghost[data-tile="guardrail"] .drag-ghost-rail {
  fill: var(--guardrail);
}
.drag-ghost[data-tile="artifact"] .drag-ghost-rail {
  fill: var(--line);
}
.drag-ghost-word,
.drag-reason-text {
  fill: var(--ink);
}
.drag-reason-box {
  fill: var(--panel);
  stroke: var(--guardrail);
}
svg g[data-drop-target="true"] .node-box {
  stroke: var(--select);
  stroke-width: 2.5;
}
svg g[data-drop-target="true"] .edge {
  stroke: var(--select);
  stroke-width: 3;
}
.editor-drawing[data-drop-target="true"] {
  outline: 2px dashed var(--select);
  outline-offset: -6px;
}
.editor-artifacts[data-drop-target="true"] {
  outline: 2px dashed var(--select);
  outline-offset: 2px;
}

/* The popover a dropped tile opens stands on the panel, ruled as a card is,
   over whatever it was dropped on. */
.drop-popover {
  background: var(--panel);
  border-color: var(--line);
  color: var(--ink);
}
/* A workflow choice in it is a row on the panel, lifting onto the band while
   it is pointed at or stood on; its path and its node count are muted, and
   so is New workflow while it can make nothing, above the hairline that
   rules it off the list. */
.workflow-choice,
.workflow-new {
  background: none;
  color: var(--ink);
  border-color: var(--hairline);
}
.workflow-choice:hover,
.workflow-choice:focus,
.workflow-new:not([aria-disabled="true"]):hover {
  background: var(--band);
}
.workflow-choice-count,
.workflow-choice-path,
.workflow-new[aria-disabled="true"] {
  color: var(--muted);
}
svg .canvas-invite {
  fill: var(--muted);
}

/* Run state, as `overlay.js` places it.

   The overlay addresses the drawing through the renderer's handle, which
   answers with the group standing for a member, a node or an edge — not with
   the shape inside it. So the overlay sets one attribute on that group and
   the paint happens here, where the rule can reach the shape: a stroke set
   on the group itself would lose to the rule above that paints the shape
   directly.

   Each rule sits below the one it overrides, and each is more specific than
   it, so a marked construct paints as run state and an unmarked one paints
   as itself. */
.member[data-overlay="run"] {
  border-color: var(--run-member);
}
svg g[data-overlay="visited"] .edge {
  stroke: var(--run-visited);
}
svg g[data-overlay="gate"] .edge {
  stroke: var(--run-gate);
}
svg g[data-overlay="current"] .node-box {
  stroke: var(--run-current);
}

/* The watch page's step panel, as `watch-step.js` builds it. A line's field
   is a label, and reads as the headings do; a gate's kind mark is `--gate`,
   the colour its barrier stands in on the canvas, so the mark and the
   barrier read as one gate. */
.watch-field {
  color: var(--muted);
}
.watch-gate-mark {
  color: var(--gate);
}

/* A file carried over the watch page, which `watch-drop.js` marks on its
   body: the canvas is outlined dashed inside its edge in `--select`, as the
   editor's canvas is where a carried tile would land. */
body[data-drop-target="true"] #graph {
  outline: 2px dashed var(--select);
  outline-offset: -6px;
}
