/* 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; }
.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; }
.node .body { padding: .35rem .5rem; font-size: .78em; color: var(--muted);
  min-height: 1.6rem; word-break: break-word; }
.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); }
