/* graph.css — the Graph pane. Rules for #panel-graph and its SVG picture only.
 *
 * Every colour comes from the palette variables, never from a literal, so the
 * picture holds up in the dark, light and candy themes without a rule per
 * theme. There are two exceptions and both are colours somebody chose rather
 * than colours a theme owns: the hue of a relation chip, which is hashed from
 * the relation's own name so one word is one colour everywhere, and a Diamond's
 * own fill, handed in as `--node-bg` from the store the rest of the app keeps
 * it in. Both arrive as custom properties with the theme's colour as the
 * fallback, so a Diamond nobody has coloured is drawn exactly as before.
 *
 * A box is a flattened hexagon rather than a rectangle: horizontal top and
 * bottom, and the two sides kinked out to a point at the middle. Those points
 * are where the lines meet it, and graph.js computes them from the same two
 * numbers this file rounds the joins with.
 *
 * The pane is now edited as well as read, so there are three more things to
 * style than there were: the strip of controls above the picture, the menu the
 * right button opens, and the small form a link is edited in. All three float
 * INSIDE the panel card, which clips, so graph.js clamps them to it rather than
 * letting one hang off an edge and disappear.
 */

/* The panel is a stage card like the AI, the Web page and a message, but it is
   not in app.css's list of them -- so it is given the same card here. */
.graph {
	min-width: 0;
	background: var(--bg-secondary);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	display: flex;
	flex-direction: column;
	overflow: hidden;
	/* The menu and the link form are positioned against this card. */
	position: relative;
}

/* The picture is panned by scrolling, and scaled only when asked: an instrument
   that quietly rescaled itself would make two stores of different sizes look
   alike, so "All" is a button and never a reflex. */
.graph-body {
	flex: 1 1 auto;
	min-height: 0;
	overflow: auto;
	padding: 4px 4px 0;
}

.graph-svg {
	display: block;
	/* SVG text inherits nothing from the page by default. */
	font-family: var(--font);
	/* A press and a sweep is a drag here, not a text selection. The mousedown
	   handler refuses the selection as well; this is the belt, so a gesture that
	   starts somewhere the handler does not run still cannot leave the picture
	   striped in highlight. The stats line below is left selectable. */
	user-select: none;
	-webkit-user-select: none;
}

/* ── Nodes ──────────────────────────────────────────────────── */

.graph-node { cursor: pointer; }

/* The box. `--node-bg` is the colour this Diamond was given elsewhere in the
   app; every rule below keeps the theme's own colour as its fallback, so the
   two kinds of Diamond differ in one property and not in a ruleset each.
   `stroke-linejoin: round` is what keeps the six corners from drawing spikes
   where the sides kink. */
.graph-node-box {
	fill: var(--node-bg, var(--bg-tertiary));
	stroke: var(--border-2);
	stroke-width: 1;
	stroke-linejoin: round;
}
/* Hover moves the outline rather than the fill when a Diamond has a colour of
   its own -- a chosen colour that changed under the pointer would read as a
   different Diamond. */
.graph-node:hover .graph-node-box {
	fill: var(--node-bg, var(--bg-hover));
	stroke: var(--accent);
}

/* An unlinked Diamond is stated, not hidden: the same box, drawn quieter. */
.graph-node.isolate .graph-node-box {
	fill: var(--node-bg, var(--bg-primary));
	stroke: var(--border);
	stroke-dasharray: 3 3;
}

/* A Diamond on a cycle is worth noticing without being alarming, so the box is
   outlined in the warning colour rather than filled with it. */
.graph-node.cycled .graph-node-box {
	stroke: var(--warn);
	stroke-width: 1.5;
}

/* Being dragged. No transition on the transform: the box has to be under the
   pointer, and a box easing towards where the pointer was is a box that is not
   where the user is holding it. */
.graph-node.dragging { cursor: grabbing; }
.graph-node.dragging .graph-node-box {
	stroke: var(--accent);
	stroke-width: 1.5;
}

/* The Diamond a link is being drawn FROM, while it is being drawn. */
.graph-node.link-source .graph-node-box {
	stroke: var(--accent);
	stroke-width: 2;
}

.graph-node-name {
	fill: var(--node-fg, var(--text-primary));
	font-size: var(--fs-sm);
	dominant-baseline: alphabetic;
}
.graph-node.isolate .graph-node-name { fill: var(--node-fg, var(--text-secondary)); }

/* The artefact count: files, pages and chats this Diamond is linked to, which
   are counted rather than drawn so they cannot crowd out the structure. */
.graph-badge-text {
	fill: var(--text-muted);
	font-size: var(--fs-3xs);
	text-anchor: end;
}
.graph-cycle-text {
	fill: var(--warn);
	font-size: var(--fs-2xs);
	text-anchor: end;
}

/* ── Edges ──────────────────────────────────────────────────── */

.graph-edge-line {
	fill: none;
	stroke: var(--text-muted);
	stroke-width: 1.4;
}
.graph-edge:hover .graph-edge-line { stroke: var(--accent); }

/* The wide invisible twin that takes the clicks. 1.4 pixels of stroke is not
   something a pointer can be asked to hit, and widening the drawn line to make
   it hittable would be letting the interaction decide how the picture looks. */
.graph-edge-hit {
	fill: none;
	stroke: transparent;
	stroke-width: 16;
	cursor: pointer;
}

/* A closing edge -- the one that makes a cycle a cycle. Dashed and in the
   warning colour, because the instrument's job is to make it visible, not to
   pretend the graph is a tree. */
.graph-edge.back .graph-edge-line {
	stroke: var(--warn);
	stroke-dasharray: 6 4;
}

.graph-arrow      { fill: var(--text-muted); }
.graph-arrow.back { fill: var(--warn); }
.graph-arrow.live { fill: var(--accent); }

/* ── The relation chips ─────────────────────────────────────── */
/* A link's relations ride its own line as pills, one per relation, stacked when
   there are several. They are the Diamonds' tag chips in another medium: the
   same hue per word, the same shape. The halo the bare word used to be painted
   with is gone with it -- a pill is opaque, so the line runs up to its edge and
   stops there, which is the join the words never had.

   The type is pinned at 11px rather than taken from `--fs-2xs`. The pill's
   width is arithmetic in graph.js, done without asking the page how wide the
   word came out, and type that floated against that arithmetic would sit in a
   pill of the wrong size. */
.graph-chip-box {
	fill: hsl(var(--tag-h, 0) 30% 20%);
	stroke: hsl(var(--tag-h, 0) 28% 34%);
	stroke-width: 1;
}
.graph-edge-label {
	fill: hsl(var(--tag-h, 0) 55% 78%);
	font-size: 11px;
	text-anchor: middle;
	dominant-baseline: central;
	pointer-events: none;
}
/* The light themes take the reverse, exactly as `.tag-chip` does in app.css:
   pale pill, dark lettering, and neither side has to know which relations
   exist. */
:root[data-ink="dark"] .graph-chip-box {
	fill: hsl(var(--tag-h, 0) 70% 94%);
	stroke: hsl(var(--tag-h, 0) 45% 80%);
}
:root[data-ink="dark"] .graph-edge-label { fill: hsl(var(--tag-h, 0) 55% 30%); }

/* ── The live arrow ─────────────────────────────────────────── */
/* The only moving thing in the pane. It follows the pointer while a link is
   being drawn and is thrown away when the gesture ends; nothing about it is
   ever stored, which is why the picture is still a function of the store. */
.graph-live-line {
	fill: none;
	stroke: var(--accent);
	stroke-width: 1.6;
	stroke-dasharray: 5 4;
	pointer-events: none;
}
.graph-live { pointer-events: none; }

/* While a link is being aimed, the pointer says what a click will do. */
.graph-body.linking { cursor: crosshair; }
.graph-body.panning { cursor: grabbing; }

/* ── Furniture ──────────────────────────────────────────────── */

.graph-band {
	fill: var(--text-muted);
	font-size: var(--fs-2xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

.graph-empty {
	margin: 0;
	padding: 14px 16px;
	color: var(--text-secondary);
	font-size: var(--fs-sm);
	line-height: 1.5;
}

.graph-stats {
	flex: none;
	padding: 6px 14px 8px;
	border-top: 1px solid var(--border);
	color: var(--text-muted);
	font-size: var(--fs-2xs);
	font-family: var(--font-mono);
}
/* The stats line is the panel's foot, not part of what scrolls. */
.graph-body > .graph-stats { position: sticky; bottom: 0; background: var(--bg-secondary); }

/* ── The control strip ──────────────────────────────────────── */
/* Above the picture and outside the scroller, so it neither scrolls away nor
   competes with the picture for the panel's own head. */
.graph-bar {
	flex: none;
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 5px 10px 6px;
	border-bottom: 1px solid var(--border);
	min-width: 0;
}

.graph-btn {
	flex: none;
	font: inherit;
	font-size: var(--fs-2xs);
	line-height: 1.4;
	padding: 3px 10px;
	border-radius: var(--radius-sm);
	border: 1px solid var(--border-2);
	background: var(--bg-tertiary);
	color: var(--text-secondary);
	cursor: pointer;
}
.graph-btn:hover {
	background: var(--bg-hover);
	color: var(--text-primary);
	border-color: var(--border-strong);
}
/* Armed. The pressed state has to read as pressed without the label changing,
   or the button says one thing and means another. */
.graph-btn.on {
	background: var(--accent-soft);
	border-color: var(--accent);
	color: var(--accent-text);
}
.graph-btn.primary {
	background: var(--accent);
	border-color: var(--accent);
	color: var(--on-fill);
}
.graph-btn.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.graph-btn.danger { color: var(--danger); }
.graph-btn.danger:hover { color: var(--danger); border-color: var(--danger); }

/* What the mode is waiting for, said where the control that armed it is. */
.graph-say {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-size: var(--fs-2xs);
	color: var(--text-muted);
}
.graph-say.warn { color: var(--warn); }

/* ── The right-click menu ───────────────────────────────────── */

.graph-menu {
	position: absolute;
	z-index: 30;
	min-width: 170px;
	padding: 4px;
	border: 1px solid var(--border-2);
	border-radius: var(--radius-sm);
	background: var(--bg-tertiary);
	box-shadow: 0 6px 22px rgba(0, 0, 0, 0.28);
}

.graph-menu-item {
	display: block;
	width: 100%;
	text-align: left;
	font: inherit;
	font-size: var(--fs-xs);
	padding: 5px 9px;
	border: 0;
	border-radius: var(--radius-sm);
	background: transparent;
	color: var(--text-primary);
	cursor: pointer;
}
.graph-menu-item:hover { background: var(--bg-hover); }
.graph-menu-item.danger { color: var(--danger); }
.graph-menu-sep {
	height: 1px;
	margin: 4px 2px;
	background: var(--border);
}

/* ── The link form ──────────────────────────────────────────── */

.graph-edit {
	position: absolute;
	z-index: 31;
	width: 260px;
	padding: 10px 12px 11px;
	border: 1px solid var(--border-2);
	border-radius: var(--radius);
	background: var(--bg-tertiary);
	box-shadow: 0 8px 26px rgba(0, 0, 0, 0.32);
}

.graph-edit-head {
	font-size: var(--fs-xs);
	color: var(--text-primary);
	margin-bottom: 8px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.graph-edit-lab {
	display: block;
	font-size: var(--fs-3xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
	margin: 6px 0 3px;
}

.graph-edit-input,
.graph-edit-note {
	display: block;
	width: 100%;
	box-sizing: border-box;
	font: inherit;
	font-size: var(--fs-xs);
	padding: 4px 7px;
	border: 1px solid var(--border-2);
	border-radius: var(--radius-sm);
	background: var(--bg-primary);
	color: var(--text-primary);
}
.graph-edit-note { resize: vertical; min-height: 40px; }
.graph-edit-input:focus,
.graph-edit-note:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ── Relations, in the form ─────────────────────────────────── */
/* The chips on the link, and under the box the ones the store already holds.
   Both rows scroll rather than grow: the form floats inside the panel card,
   which clips, and a link given six relations must not push its own buttons off
   the bottom of it. */
.graph-rel-row {
	display: flex;
	flex-wrap: wrap;
	gap: 4px;
	margin-top: 3px;
	max-height: 62px;
	overflow-y: auto;
}
.graph-rel-add {
	display: flex;
	align-items: center;
	gap: 6px;
	margin-top: 6px;
}
.graph-rel-add .graph-edit-input { flex: 1 1 auto; min-width: 0; }
.graph-rel-add .graph-btn { flex: none; padding: 3px 9px; }

.graph-rel-none {
	font-size: var(--fs-3xs);
	color: var(--text-muted);
}
.graph-rel-pool-lab {
	margin-top: 8px;
	font-size: var(--fs-3xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
}

/* Said when a relation will not fit, beside the box it was refused from. In the
   warning colour and not the muted one: it is the reason a save did not happen,
   which is not a footnote. */
.graph-rel-hint {
	margin-top: 7px;
	font-size: var(--fs-3xs);
	line-height: 1.45;
	color: var(--warn);
}

.graph-edit-row {
	display: flex;
	gap: 6px;
	margin-top: 10px;
}

/* The link editor's title row: the pair being linked, and the way out. The head
   ellipsises rather than pushing the cross off the end of a 260px box, which is
   what `min-width: 0` on a flex child is for. The row carries the space the head
   used to carry itself. */
.graph-edit-top { margin-bottom: 8px; }
.graph-edit-top .graph-edit-head { margin-bottom: 0; min-width: 0; }
