/* spdag. One stylesheet, no framework, no CDN -- same as the other konzalabs
   Django apps. Dark, because a node canvas is easier to read on one. */

:root {
  --bg: #14181e;
  --surface: #1c222b;
  --surface-2: #242c37;
  --line: #313b48;
  --ink: #e6e9ee;
  --muted: #96a0af;
  --accent: #b27c2c;
  --accent-ink: #1b1205;
  --ok: #4f9d69;
  --err: #c0553f;
  --radius: 6px;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}

a { color: #7fb3e8; }
a:hover { color: #a8cdf3; }
code, pre { font-family: var(--mono); font-size: .9em; }
hr { border: 0; border-top: 1px solid var(--line); margin: 1.2rem 0; }
h1 { font-size: 1.5rem; margin: 0 0 .6rem; }
h2 { font-size: 1.15rem; margin: 1.4rem 0 .5rem; }
h3 { font-size: 1rem; margin: 1.1rem 0 .4rem; }

.topbar {
  display: flex; align-items: center; gap: 1.5rem;
  padding: .6rem 1rem; background: var(--surface); border-bottom: 1px solid var(--line);
}
.brand { font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent); text-decoration: none; }
.brandblock { display: flex; align-items: baseline; gap: .5rem; }
.brandblock .version { font-family: var(--mono); font-size: .75em; color: var(--muted); }
.topbar nav { display: flex; gap: 1rem; align-items: center; margin-left: auto; }
.topbar nav a { text-decoration: none; }

main { padding: 1.2rem; }
.panel { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.2rem; max-width: 1200px; margin: 0 auto; }
.panel.narrow { max-width: 460px; }
.crumb { margin-top: 0; }
.muted { color: var(--muted); }
.small { font-size: .86em; }
.num { text-align: right; font-variant-numeric: tabular-nums; }

.messages { list-style: none; margin: 0; padding: .5rem 1rem 0; }
.msg { padding: .5rem .8rem; border-radius: var(--radius); margin-bottom: .4rem;
  background: var(--surface-2); border-left: 3px solid var(--muted); }
.msg.success { border-left-color: var(--ok); }
.msg.error { border-left-color: var(--err); }
.msg.warning { border-left-color: var(--accent); }
.notice { background: var(--surface-2); border-left: 3px solid var(--accent);
  padding: .6rem .8rem; border-radius: var(--radius); }
.error { color: var(--err); }
.error-box { background: #211614; border: 1px solid var(--err); padding: .8rem;
  border-radius: var(--radius); overflow-x: auto; white-space: pre-wrap; }

button, .button {
  font: inherit; padding: .4rem .8rem; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--surface-2); color: var(--ink);
  cursor: pointer; text-decoration: none; display: inline-block;
}
button:hover, .button:hover { border-color: var(--muted); }
button.primary, .button.primary { background: var(--accent); color: var(--accent-ink);
  border-color: var(--accent); font-weight: 600; }
button.danger { border-color: #5a2f28; color: #e39b8c; }
button.small { padding: .2rem .5rem; font-size: .85em; }
button.wide { width: 100%; margin-top: .4rem; }
button.linkish { background: none; border: none; color: #7fb3e8; padding: 0; }

input, select, textarea {
  font: inherit; padding: .35rem .5rem; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--bg); color: var(--ink); width: 100%;
}
input[type=checkbox] { width: auto; }
label.field { display: block; margin: .5rem 0; font-size: .88em; color: var(--muted); }
label.field input, label.field select, label.field textarea { margin-top: .2rem; color: var(--ink); }
label.check { display: flex; gap: .4rem; align-items: center; font-size: .9em; }
form.inline { display: inline; }
.row { display: flex; gap: .6rem; align-items: flex-end; flex-wrap: wrap; margin-top: 1rem; }
.row input { width: auto; min-width: 14rem; }

table.grid { width: 100%; border-collapse: collapse; margin: .8rem 0; }
table.grid th, table.grid td { text-align: left; padding: .4rem .6rem;
  border-bottom: 1px solid var(--line); vertical-align: top; }
table.grid th { color: var(--muted); font-weight: 600; font-size: .85em;
  text-transform: uppercase; letter-spacing: .04em; }
table.kv th { width: 10rem; }
table.tracks tbody tr:hover { background: var(--surface-2); }

.tag { display: inline-block; padding: 0 .4rem; border-radius: 3px; font-size: .78em;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--muted); }
.tag.ok, .tag.written { color: #9fe0b5; border-color: #2f5c3f; }
.tag.error { color: #f0a595; border-color: #5a2f28; }

ol.log { color: var(--muted); font-size: .9em; }
table.nodes details summary { cursor: pointer; color: #7fb3e8; }
ol.node-items { margin: .4rem 0 0; padding-left: 1.4rem; font-size: .88em;
  max-height: 18rem; overflow-y: auto; }
ol.node-items li { padding: .1rem 0; }
ul.runs { list-style: none; padding: 0; font-size: .88em; }
ul.runs li { padding: .2rem 0; display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; }
.write-box { background: var(--surface-2); border-radius: var(--radius);
  padding: .8rem; margin: 1rem 0; }

/* ---------------------------------------------------------------- editor */

body.editor-page main { padding: 0; }
.editor { display: grid; grid-template-columns: 250px 1fr 300px;
  height: calc(100vh - 47px); }
.palette, .inspector { background: var(--surface); padding: .9rem;
  overflow-y: auto; border-right: 1px solid var(--line); }
.inspector { border-right: 0; border-left: 1px solid var(--line); }
.palette h2 { margin-top: 0; font-size: 1.05rem; }
.palette-group { margin-bottom: .7rem; }
.palette-group h4 { margin: .5rem 0 .3rem; font-size: .74em; color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; }
.palette-group button { display: block; width: 100%; text-align: left;
  margin-bottom: .25rem; font-size: .88em; }

/* The wrap is the viewport; the canvas is the world, moved and scaled by one
   transform. overflow is hidden rather than auto because scrollbars and a pan
   transform would be two ways to say the same thing and would fight. */
.canvas-wrap { position: relative; overflow: hidden; background: var(--bg); }
.canvas { position: relative; width: 3000px; height: 2000px; transform-origin: 0 0;
  background: radial-gradient(circle, var(--line) 1px, transparent 1px) 0 0 / 24px 24px; }
.canvas-wrap.grabbable { cursor: grab; }
.canvas-wrap.panning { cursor: grabbing; }

.canvas-tools { position: absolute; right: .6rem; bottom: .6rem; z-index: 10;
  display: flex; gap: .25rem; padding: .25rem; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 2px 8px #0006; }
.canvas-tools button { padding: .15rem .45rem; font-size: .85em; min-width: 2.1rem;
  font-variant-numeric: tabular-nums; }

svg.edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
/* non-scaling-stroke: the wires live inside the zoomed canvas, and a 2px line at
   30% would be a hairline and its click target six pixels wide. */
svg.edges path.wire { fill: none; stroke: #5d6b7d; stroke-width: 2;
  vector-effect: non-scaling-stroke; }
svg.edges path.hit { fill: none; stroke: transparent; stroke-width: 14;
  vector-effect: non-scaling-stroke; pointer-events: stroke; cursor: pointer; }
svg.edges g.edge:hover path.wire { stroke: var(--err); }
svg.edges path.dragging { stroke: var(--accent); stroke-dasharray: 5 4; }

.node { position: absolute; width: 190px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 2px 8px #0006; user-select: none; }
.node.selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
/* The whole node drags, so the whole node says so. */
.node { cursor: move; }
.node .title { padding: .35rem .5rem; font-size: .85em; font-weight: 600;
  border-bottom: 1px solid var(--line); border-radius: var(--radius) var(--radius) 0 0; }
/* Key/value rows, not a sentence. The body is parameters, and parameters are a
   two-column thing; run together into prose they wrapped to five lines and the
   box grew to fit, because node height is measured from what rendered. Each row
   is one line and clips, so a node's height is its row COUNT. */
.node .body { padding: .3rem .5rem; font-size: .78em; color: var(--muted);
  display: flex; flex-direction: column; gap: .1rem; }
/* NOT `.row` -- that is the form-layout class above, and it carries a 1rem
   top margin that turned three 16px rows into a 108px body. */
.node .body .kv { display: flex; gap: .5rem; justify-content: space-between;
  align-items: baseline; line-height: 1.3; }
.node .body .k { flex: none; }
.node .body .v { color: var(--ink); text-align: right;
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* A node with nothing set falls back to one line saying what it does. */
.node .body .kv.lone { display: block; }
.node .counts { padding: .15rem .5rem .3rem; font-size: .72em; color: var(--muted);
  font-variant-numeric: tabular-nums; }
/* A node that emitted nothing is the answer to "why is my playlist empty". */
.node .counts.empty { color: var(--err); }
/* Numbers describing the graph as it was BEFORE the current edit. Shown faintly
   rather than hidden: the last thing you knew beats nothing, as long as it does
   not pretend to be current. */
.node .counts.stale { opacity: .4; font-style: italic; }

#preview-status.bad { color: var(--accent); }
ol.emitted { list-style: decimal; margin: .3rem 0 .6rem; padding-left: 1.6rem;
  font-size: .84em; max-height: 16rem; overflow-y: auto; }
ol.emitted li { padding: .1rem 0; }
ol.emitted .who { display: block; color: var(--muted); font-size: .92em; }
.node.cat-source .title { background: #1f3040; }
.node.cat-transform .title { background: #322a1c; }
.node.cat-combine .title { background: #1f3229; }
.node.cat-order .title { background: #2c2436; }
.node.cat-sink .title { background: #3a2420; }

/* Ports. A node takes input on its TOP and LEFT edges, and emits from its RIGHT
   and BOTTOM -- one logical input and one logical output, each exposed on two
   sides so a graph reads left-to-right or top-to-bottom, whichever the layout
   wants.

   Every port carries an arrow pointing the way the list travels THROUGH it:
   inbound ports point into the node, outbound ports point away. Drawn with
   borders rather than a glyph so it is a real triangle at a real size, not a
   character the font renders at 9px and hopes for the best. */
.port {
  position: absolute; width: 20px; height: 20px; border-radius: 50%;
  cursor: crosshair; border: 2px solid; display: flex;
  align-items: center; justify-content: center;
}
.port::after {
  content: ""; width: 0; height: 0;
  border-left: 8px solid currentColor;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  margin-left: 2px;
}
/* Inbound: blue, pointing into the node. */
.port.in { background: #22303f; border-color: #6f96bd; color: #b7d6f5; }
.port.in.left { left: -11px; top: 12px; }
.port.in.top { top: -11px; left: 50%; margin-left: -10px; }
.port.in.top::after { transform: rotate(90deg); margin: 2px 0 0 0; }
/* Outbound: amber, pointing away from the node. */
.port.out { background: #3a2f1c; border-color: #c2a05e; color: #f0d79a; }
.port.out.right { right: -11px; top: 12px; }
.port.out.bottom { bottom: -11px; left: 50%; margin-left: -10px; }
.port.out.bottom::after { transform: rotate(90deg); margin: 2px 0 0 0; }

.port:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.node.armed .port.out { background: var(--accent); border-color: var(--accent);
  color: var(--accent-ink); }

/* Searchable picker (the channel field). */
.lookup { position: relative; }
.lookup-chosen { font-size: .82em; color: var(--ink); margin-top: .15rem; min-height: 1em; }
.lookup-chosen.unknown { color: var(--accent); }
.lookup-list {
  list-style: none; margin: .2rem 0 0; padding: 0; position: absolute; z-index: 20;
  left: 0; right: 0; max-height: 15rem; overflow-y: auto;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 6px 18px #0008;
}
.lookup-list li {
  padding: .3rem .5rem; cursor: pointer; display: flex; gap: .5rem; align-items: baseline;
}
.lookup-list li.active, .lookup-list li:hover { background: var(--accent); color: var(--accent-ink); }
.lookup-list li .name { flex: 1; }
.lookup-list li .slug { font-family: var(--mono); font-size: .78em; opacity: .65; }
.lookup-list li.empty { color: var(--muted); cursor: default; font-style: italic; }
.lookup-list li.empty:hover { background: none; color: var(--muted); }

.inspector .inputs { list-style: none; padding: 0; font-size: .85em; }
.inspector .inputs li { display: flex; gap: .3rem; align-items: center;
  padding: .2rem 0; border-bottom: 1px solid var(--line); }
.inspector .inputs li span { flex: 1; }
.help { font-size: .82em; color: var(--muted); margin: .4rem 0 .8rem; }
kbd { font-family: var(--mono); font-size: .85em; padding: 0 .25rem;
  border: 1px solid var(--line); border-radius: 3px; background: var(--surface-2); }
