/* The viewer's structure and sizes, and no colour value at all.

   This is one half of what `viewer/viewer.css` used to be. The split is not
   cosmetic: `viewer/theme.css` is what a replacement renderer is expected to
   honour, so that one restyle covers every renderer, while this file keys its
   rules to the built-in renderer's own element vocabulary — `.node-box`,
   `.edge`, `.member`, the panel ids the page's own scripts write — and cannot
   bind markup it did not write. A replacement renderer may supply its own
   structural rules in place of this file; it may not supply its own theme.
   `docs/dev/viewer-renderer-seam.md` states that division.

   Every declaration here is a layout, a spacing, a type size or a stroke
   width. A colour — a hex value, a colour function, a named colour, or any
   property that paints — belongs in `theme.css`, and
   `crates/flow-cli/tests/it/viewer_assets/colour_tokens.rs` fails if one
   lands here. That is what keeps the two halves from drifting back into
   one. */

/* The three type sizes, and the only three: a title, the body, and the
   small size for everything read beside the body rather than as it — a
   status line, a diagnostic, a label on the canvas. They are tokens here,
   because a size is structure, and every `font-size` under `viewer/` reads
   one of them. The small one is the canvas's own text size, which
   `render.js` measures a label by before it is drawn, so it is a pixel size
   rather than one that moves with the reader's default. */
:root {
  --size-title: 18px;
  --size-body: 14px;
  --size-small: 12px;
}

body {
  font-family: system-ui, sans-serif;
  font-size: var(--size-body);
  line-height: 1.5;
}

/* A page read as a document — the viewer — keeps a reading measure and the
   margins around it. A page that is an app, `body.app`, fills the window
   instead, as the rule below it says, and its own stylesheet says which
   rows it has. */
body:not(.app) {
  margin: 0 auto;
  max-width: 72rem;
  padding: 1.5rem 1.25rem 4rem;
}

/* The app shell the two served pages stand in, the editor and the gallery:
   one grid exactly the height of the window, as wide as the window, which
   never scrolls. Every region that can outgrow its row scrolls inside
   itself. */
body.app {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  height: 100vh;
  margin: 0;
  overflow: hidden;
}

/* The bar both served pages stand under: one line, whatever the status line
   says. The status line takes the room the controls leave and cuts what
   does not fit, rather than wrapping the bar onto a second line or pushing
   a control off the window. What went wrong is read whole — how to open the
   page, why a request was refused — so a failure wraps, and the row beneath
   gives up the height. */
.editor-bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
  padding: 0.4rem 1rem;
}
.editor-bar h1 {
  margin: 0;
}
.editor-bar .status {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.editor-bar .status[data-state="failed"] {
  white-space: normal;
}
.editor-crumbs .breadcrumb {
  margin: 0;
}
h1 {
  font-size: var(--size-title);
  margin: 0 0 0.25rem;
}
h2 {
  font-size: var(--size-small);
  margin: 1.75rem 0 0.5rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
h3 {
  font-size: var(--size-body);
  margin: 0 0 0.25rem;
}
p.lede,
p.hint {
  margin: 0.25rem 0 0.75rem;
}
code {
  font-family: ui-monospace, monospace;
}
textarea {
  display: block;
  width: 100%;
  margin: 0.5rem 0;
  font-family: ui-monospace, monospace;
  font-size: var(--size-small);
}
button {
  font: inherit;
  padding: 0.3rem 0.9rem;
}

/* The controls of the editor's two panels, the rail and the inspector, are
   the Workbench's ghost controls: the small type in a hairline box one line
   of text tall, never a browser-default button in the body size. A button
   here outweighs the rule above; every other rule naming a control's own
   class outweighs these, since the panels are named through `:where()`.
   What they are painted with is `theme.css`'s. */
:where(.editor-rail, .editor-inspector) button,
:where(.editor-rail, .editor-inspector) :where(input:not([type="checkbox"]), select, textarea) {
  box-sizing: border-box;
  border-width: 1px;
  border-style: solid;
  border-radius: 4px;
  font: inherit;
  font-size: var(--size-small);
  line-height: 1.4;
}
:where(.editor-rail, .editor-inspector) button {
  padding: 0.1rem 0.5rem;
  cursor: pointer;
}
:where(.editor-rail, .editor-inspector) button:disabled {
  cursor: default;
}
:where(.editor-rail, .editor-inspector) :where(input:not([type="checkbox"]), select) {
  max-width: 100%;
  padding: 0.1rem 0.35rem;
}

/* The bar's controls — the validity chip, Compile, the theme control, the
   link to the other page, Save and Quit — are ghost controls of the same
   kind, a little wider since the bar has the room, on both served pages.
   They are named as the bar's own children, so the trail back out of a
   door, which stands inside the breadcrumb, keeps its link's shape. The
   link is shaped as the buttons beside it, with no underline. The validity
   chip is a pill, so it reads as a state rather than as a command. What
   they are painted with is `theme.css`'s. */
:where(.editor-bar, .editor-session) > button,
:where(.editor-bar) > .page-link {
  box-sizing: border-box;
  border-width: 1px;
  border-style: solid;
  border-radius: 4px;
  padding: 0.2rem 0.7rem;
  font: inherit;
  font-size: var(--size-small);
  line-height: 1.4;
  text-decoration: none;
  cursor: pointer;
}
:where(.editor-bar, .editor-session) > button:disabled {
  cursor: default;
}
.validity-chip {
  border-radius: 999px;
}
.status {
  margin: 0.5rem 0 0;
  font-size: var(--size-small);
}

/* The follow controls: granting a document, granting its directory, and the
   explicit re-read a browser with no re-readable handle is left with. They
   are one row because they are one decision — how this document reaches the
   page — and they wrap rather than shrink, because a control a reader cannot
   read the label of is one they will not press. */
p.follow-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.5rem 0 0;
}

/* The two diagnostics panels: the page's own, and the one the run-state
   panel builds for itself in `overlay.js`. Both are shaped here rather than
   by a `style` property that module sets on the element, which no stylesheet
   could have overridden. */
#diagnostics,
#overlay-diagnostics {
  list-style: none;
  margin: 0;
  padding: 0;
}
#diagnostics li,
#overlay-diagnostics li {
  border-left-width: 3px;
  border-left-style: solid;
  margin: 0.35rem 0;
  padding: 0.25rem 0.6rem;
  font-size: var(--size-small);
}
#diagnostics .code,
#overlay-diagnostics .code {
  font-family: ui-monospace, monospace;
  font-weight: 600;
}
/* The canvas, the trail back out of the doors it was opened through, and the
   control that asks for the artifact paths.

   These four are HTML, and that is why they have rules at all. Every shape
   inside the `<svg>` carries its own sizes and its own `theme.css` token as
   presentation attributes the renderer writes — which is what lets a
   replacement renderer honour this theme without also writing this
   renderer's classes — but a `<nav>`, a `<button>` and a `<p>` take no such
   attribute. For the HTML the drawing hangs on, a rule in this file and its
   half in `theme.css` are the whole of the appearance there is. */
.canvas {
  display: flow-root;
}
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem;
  margin: 1rem 0 0;
  font-family: ui-monospace, monospace;
  font-size: var(--size-small);
}

/* A step of the trail that is behind you is a button, because pressing it
   swaps the canvas; it is shaped as the link it reads as, so that the trail
   reads as one line rather than as a row of controls. The member standing on
   the canvas is not a button at all — there is nowhere for it to go. */
.breadcrumb-back {
  padding: 0;
  border-width: 0;
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.breadcrumb-here {
  font-weight: 700;
}
.breadcrumb-separator {
  /* Punctuation between two paths rather than part of either, so a reader
     dragging the trail to copy a path does not take it with them. */
  user-select: none;
}

/* The density control sits inside the member card — or, on the editor, in
   its status bar — rather than beside the page's own controls, so it is the
   smaller of the two sizes a button here is drawn at. */
.density {
  margin: 0.5rem 0 0;
}
.density-control {
  padding: 0.15rem 0.6rem;
  font-size: var(--size-small);
}
.member {
  border-width: 1px;
  border-style: solid;
  border-radius: 6px;
  margin: 1rem 0;
  padding: 0.75rem 1rem 1rem;
  overflow-x: auto;
}
.member dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.1rem 0.75rem;
  margin: 0 0 0.5rem;
  font-size: var(--size-small);
}
.member dd {
  margin: 0;
  font-family: ui-monospace, monospace;
}
.scope-guardrails {
  font-size: var(--size-small);
  margin: 0 0 0.5rem;
}
svg text {
  font-family: ui-monospace, monospace;
  font-size: var(--size-small);
  line-height: 1.2;
}

/* The legend: one line of entries, each what the canvas draws — in a small
   `<svg>` of its own, drawn by the same function as on the canvas — beside
   the word for it. The line wraps rather than pushing an entry off the
   page. */
.legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--size-small);
}
.legend-entry {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.legend-swatch {
  display: block;
  overflow: visible;
}
.legend-word {
  white-space: nowrap;
}

/* The recessed canvas a member's cards stand on, rounded like the member
   around it. What it is painted with is `theme.css`'s business. */
.member svg {
  border-radius: 4px;
}

/* A card's outline is one weight and one line whatever the node's kind: the
   kind rides the rail down its left edge, and an outline drawn differently
   per kind would say it twice. */
svg .node-box {
  stroke-width: 1;
}
svg .edge {
  stroke-width: 1.5;
}

/* The drawing vocabulary's words. Every shape `render.js` draws — the kind
   rail, the identity band, the hairline rules, a chip, a barrier, a bound
   pill, a dashed loop back — states its own geometry, dash and token as
   attributes, so a replacement renderer can honour the theme without these
   classes; what an attribute does not carry is the type of the words on
   them, and that is set here, for the viewer and the editor alike. The id
   heads its band in the heaviest weight; `runs <skill>` is a sentence and
   is set as prose rather than as an identifier; the `in` and `out` labels on
   their rules are spaced small labels; a barrier's word is as firm as the
   stop it names. */
svg .node-id {
  font-weight: 700;
}
svg .sentence-text {
  font-family: system-ui, sans-serif;
}
svg .side-label {
  font-weight: 600;
  letter-spacing: 0.08em;
}
svg .gate-word {
  font-weight: 600;
}
/* A binding's dot is ringed where it meets the card's border. */
svg .port-dot {
  stroke-width: 1.5;
}

/* The editor's selection reads as a heavier outline than any card's own. */
svg g[data-selected="true"] .node-box,
svg g[data-selected="true"] .edge {
  stroke-width: 2.5;
}

/* Run state, as `overlay.js` marks it: the weights that make a marked
   construct read as marked. What it is painted with is `theme.css`'s
   business, and the tokens there are run state's own. */
svg g[data-overlay="visited"] .edge {
  stroke-width: 2.75;
}
svg g[data-overlay="gate"] .edge {
  stroke-width: 2.75;
  stroke-dasharray: 6 4;
}
svg g[data-overlay="current"] .node-box {
  stroke-width: 3.5;
}
