2026-02-19T12-37-24_auto_memory/memories.db-shm, memory/memories.db-wal, me

This commit is contained in:
Nicholai Vogel 2026-02-19 05:37:24 -07:00
parent 043b32060b
commit a4de8f409a
5 changed files with 300 additions and 71 deletions

View File

@ -1413,3 +1413,15 @@
{"timestamp":"2026-02-19T12:32:12.315Z","level":"info","category":"watcher","message":"File added","data":{"path":"/home/nicholai/.agents/memory/memories.db-wal"}}
{"timestamp":"2026-02-19T12:32:12.317Z","level":"info","category":"watcher","message":"File changed","data":{"path":"/home/nicholai/.agents/memory/memories.db-wal"}}
{"timestamp":"2026-02-19T12:32:12.327Z","level":"info","category":"watcher","message":"File changed","data":{"path":"/home/nicholai/.agents/memory/memories.db"}}
{"timestamp":"2026-02-19T12:32:17.330Z","level":"warn","category":"git","message":"Git add failed"}
{"timestamp":"2026-02-19T12:32:17.509Z","level":"info","category":"git","message":"Auto-committed","data":{"message":"2026-02-19T12-32-17_auto_memory/memories.db-shm, memory/memories.db-wal, me","filesChanged":4}}
{"timestamp":"2026-02-19T12:33:17.933Z","level":"info","category":"git","message":"Git push","data":{"commits":4}}
{"timestamp":"2026-02-19T12:37:12.019Z","level":"warn","category":"git","message":"Periodic sync failed: No git credentials found. Run `gh auth login` or set GITHUB_TOKEN secret."}
{"timestamp":"2026-02-19T12:37:19.528Z","level":"info","category":"watcher","message":"File added","data":{"path":"/home/nicholai/.agents/memory/memories.db-shm"}}
{"timestamp":"2026-02-19T12:37:19.528Z","level":"info","category":"watcher","message":"File added","data":{"path":"/home/nicholai/.agents/memory/memories.db-wal"}}
{"timestamp":"2026-02-19T12:37:19.530Z","level":"info","category":"watcher","message":"File changed","data":{"path":"/home/nicholai/.agents/memory/memories.db-wal"}}
{"timestamp":"2026-02-19T12:37:19.546Z","level":"info","category":"watcher","message":"File changed","data":{"path":"/home/nicholai/.agents/memory/memories.db"}}
{"timestamp":"2026-02-19T12:37:19.528Z","level":"info","category":"watcher","message":"File added","data":{"path":"/home/nicholai/.agents/memory/memories.db-shm"}}
{"timestamp":"2026-02-19T12:37:19.528Z","level":"info","category":"watcher","message":"File added","data":{"path":"/home/nicholai/.agents/memory/memories.db-wal"}}
{"timestamp":"2026-02-19T12:37:19.530Z","level":"info","category":"watcher","message":"File changed","data":{"path":"/home/nicholai/.agents/memory/memories.db-wal"}}
{"timestamp":"2026-02-19T12:37:19.546Z","level":"info","category":"watcher","message":"File changed","data":{"path":"/home/nicholai/.agents/memory/memories.db"}}

Binary file not shown.

View File

@ -12,6 +12,8 @@ description: >
# Signet Design System
See `assets/design-brief.png` for a full-page reference screenshot.
## Aesthetic Direction
Technical. Industrial. Near-monochrome. The visual language draws from
@ -29,6 +31,34 @@ Core principles:
- **Registration marks** — crosshair (+) elements as visual punctuation
- **Generative texture** — Bayer-dithered halftone art as bold compositional elements
- **Dual theme** — dark (near-black) and light (warm beige cream)
- **Background opacity 0.8** — all overlay elements (connectors, nodes,
schematic circles) sit at 80% opacity to recede behind content
## Anti-Patterns
These violate the design system. Never do:
- **Border-radius** — no rounded corners, no pills, no circles on
containers. Only exception: schematic decorator circles.
- **Filled buttons at rest** — buttons are always outlined, fill only
on hover (invert).
- **Generic fonts** — no Inter, Roboto, Arial, system-ui. Only Chakra
Petch and IBM Plex Mono.
- **Serif fonts** — never. Too editorial, too warm.
- **Saturated color** — no bright blues, greens, purples. The palette
is desaturated gray with muted danger/success accents only.
- **Purple gradients** — the quintessential AI slop aesthetic. Banned.
- **Soft/friendly UI** — no rounded icons, no emoji glyphs, no pastel
tones, no bouncy animations.
- **Opacity on muted text** — use `--color-text-muted` directly,
never layer `opacity: 0.5` on top of already-muted colors.
- **Pure white backgrounds** — light theme uses warm beige `#e4dfd8`,
never `#ffffff`.
- **Single-font hierarchy** — always pair display + mono. Using one
font for everything flattens the hierarchy.
- **Attention-grabbing animation** — no bounce, no scale-up entrances,
no flash effects. Ambient only.
- **S-curves or multi-inflection connector lines** — connectors use
single-curve quadratic beziers. Never cubic with two control points.
## Typography
@ -56,10 +86,6 @@ CSS variables:
--font-mono: 'IBM Plex Mono', monospace;
```
Anti-patterns: using a single font for everything (lacks hierarchy),
serif fonts, generic sans (Inter, Roboto, Arial), any font that
feels too polished for the raw industrial aesthetic.
## Design Tokens
See `assets/globals.css` for the full token set. All tokens use CSS
@ -89,8 +115,7 @@ Light background is warm beige, never pure white. Borders flip from
white-alpha to black-alpha. Dither dots flip color via `--color-dither`.
Readability: light mode `--color-text-muted` must be dark enough for
legible text on the beige surface (~3.5:1 contrast min). Avoid
layering opacity on top of muted-colored text — use the token directly.
legible text on the beige surface (~3.5:1 contrast min).
## Icon System
@ -151,17 +176,92 @@ Stroked containers with monospace text. `.badge-accent` variant
uses bright text + bright border. Can include inline icons.
### Inputs
Monospace, surface background, strong border. Focus brightens border.
Select uses custom SVG chevron background-image.
Monospace, surface background, strong border. Focus brightens border
and adds 1px `box-shadow` ring. Select uses custom SVG chevron
background-image. `border-radius: 0` always.
### Section Panels
Content sections wrap in `.section-panel``--color-surface` bg,
`--color-border` stroke, 6px corner L-bracket registration marks
via `::before`/`::after`. Elevates content above the background
layers.
### Decorative Elements
- Crosshair marks (`.crosshair`) — 10px, muted
- Crosshair marks (`.crosshair`) — 10px, muted color
- 4-pointed star (`.star-4`) — clip-path polygon
- Schematic circles — stroked circles with centered crosshairs
- Connector lines — 1px lines with dot endpoints
- Corner star markers (`****`)
- Schematic circles — stroked circles with centered crosshairs,
`opacity: 0.8`, `node-pulse` animation
- Corner star markers (`****`) — mono text, positioned absolute
- Vertical sidebar text (`writing-mode: vertical-rl`)
- Numbered index grids (0023 in 8-column grid)
- Inverted labels (`.label-inv`) — bright bg, dark text, no radius
## Embedding Graph Overlay
A background layer of interconnected crosshair nodes arranged as
an embedding/vector-space visualization. Inspired by Detroit
Underground record sleeve schematics.
### Node Layout
16 crosshair nodes (`#ch-0` through `#ch-15`) in 4 clusters plus
outliers, positioned with `position: absolute` percentages:
- **Cluster A** (upper-left, near `#sc-1`): ch-0 through ch-3
- **Cluster B** (upper-right, sparse): ch-4, ch-5
- **Cluster C** (mid-left, near `#sc-2`): ch-6 through ch-8
- **Cluster D** (lower region): ch-9 through ch-12
- **Outliers**: ch-13, ch-14, ch-15 (bridge nodes between clusters)
3 hub nodes as larger schematic circles (`#sc-1`, `#sc-2`, `#sc-3`)
with crosshair pseudo-elements and `node-pulse` animation.
3 star markers (`#sm-1`, `#sm-2`, `#sm-3`) as `****` text.
### Edge Topology
Edges defined as `[fromId, toId, style, label?]` array:
- **Intra-cluster** — short connections within each cluster
- **Inter-cluster bridges** — longer connections between clusters,
labeled with embedding metrics (`cos=0.74`, `d=0.12`)
- **Hub connections** — edges from crosshair nodes to schematic
circles and star markers
### Connector Rendering
JS-drawn SVG overlay. On load (and resize), reads
`getBoundingClientRect` centers of all nodes, then draws
**quadratic bezier curves** (`Q` command) between them:
```js
const dx = b.x - a.x, dy = b.y - a.y;
const mx = (a.x + b.x) / 2 + dy * 0.12;
const my = (a.y + b.y) / 2 - dx * 0.12;
path.setAttribute('d', `M ${a.x},${a.y} Q ${mx},${my} ${b.x},${b.y}`);
```
Single control point offset 12% perpendicular from the midpoint.
This produces exactly one gentle arc per line — never S-curves.
Connector styles:
- `conn-dashed``stroke-dasharray: 6 4`, animated `dash-flow` 12s
- `conn-dashed-rev` — same dash, reverse direction 15s
- `conn-dot` — 2px filled circles at each endpoint
- `conn-label` — 7px mono text near midpoint (`d=0.12`, `cos=0.74`)
- All connectors render at `opacity: 0.8` base
### Node Hover Interaction
Crosshair nodes have `pointer-events: auto` (rest of the field is
`pointer-events: none`). On hover:
1. **Crosshair expansion**`::before`/`::after` pseudo-elements
scale 1.8x on their respective axes, color brightens to
`--color-text`
2. **SLOT label**`SLOT_XX` micro-tooltip appears above node.
6px mono text, bordered container with `--color-bg` background
3. **Ring pulse** — circular ring (`.ch-ring`) scales from 0.6 to
1.0 and fades in at 50% opacity around the node
4. **Connected edge highlight** — JS builds adjacency map from
edge list. On hover, all SVG elements belonging to connected
edges get `.conn-highlight` class: stroke brightens to
`--color-text`, width increases to 1.5px, dots/labels brighten
5. **On mouseleave** — all `.conn-highlight` classes removed
## Generative Art
@ -180,29 +280,29 @@ Use `--color-dither` so canvases adapt to theme.
## Depth & Layering
The design uses multiple overlapping layers to create spatial depth,
inspired by flight terminal / CRT interface aesthetics:
Multiple overlapping layers create spatial depth, inspired by
flight terminal / CRT interface aesthetics:
1. **SVG noise grain**`body::before`, fixed, z-index 9999 (0.04/0.06 opacity)
2. **Bleed typography** — massive text (1220rem) bleeding off viewport edges,
2.5% opacity, `--font-display`, provides subliminal brand reinforcement
3. **Floating panels** — 46 translucent rectangles (`--color-surface` and
`--color-surface-raised`) at 1550% opacity, positioned at various
depths around the viewport. Creates the layered glass-panel effect.
4. **Metadata fragments** — scattered timestamps, coordinates, version codes,
reference numbers in 7px mono text at 35% opacity. Both functional
texture and information-density signifier.
5. **Schematic overlay** — circles with crosshairs, connector lines, star markers
6. **Crosshair field** — repeating + marks at very low opacity
1. **SVG noise grain**`body::before`, fixed, z-index 9999
(0.04/0.06 opacity)
2. **Bleed typography** — massive text (1220rem) bleeding off
viewport edges, 2.5% opacity, `--font-display`
3. **Floating panels** — 46 translucent rectangles at 1550%
opacity, drifting ±34px over 4065s cycles
4. **Metadata fragments** — scattered timestamps, coordinates,
version codes in 7px mono text at 35% opacity
5. **Embedding graph** — crosshair nodes + quadratic bezier
connectors at 80% opacity (see section above)
6. **Schematic overlay** — circles with crosshairs, star markers
7. **Right edge dither** — 240px canvas strip, organic noise
8. **Section panels** — content wrapped in `--color-surface` panels with
corner registration marks (6px L-brackets). Elevates content above
the background layers.
8. **Section panels** — content wrapped in `--color-surface`
panels with corner registration marks
## Animation & Interaction
All animations are subtle and ambient — never attention-grabbing. The
goal is to create the feeling of a live, breathing system dashboard.
All animations are subtle and ambient — never attention-grabbing.
The goal is to create the feeling of a live, breathing system
dashboard.
### Ambient (CSS keyframes)
- **Floating panel drift**`fp-drift` 4065s ease-in-out infinite
@ -210,33 +310,33 @@ goal is to create the feeling of a live, breathing system dashboard.
- **Metadata flicker**`mf-flicker` 715s. Brief opacity dip to
simulate data refresh. Stagger child delays.
- **SVG connector dash flow**`dash-flow` / `dash-flow-rev` on
`stroke-dasharray: 6 4` paths, 1215s linear infinite. Dashed
bezier curves and straight lines with endpoint dots.
`stroke-dasharray: 6 4` paths, 1215s linear infinite.
- **Schematic node pulse**`node-pulse` 58s, scale 1→1.04 and
opacity 1→0.7 on circles with crosshairs.
opacity 1→0.7 on hub circles with crosshairs.
- **Scan line sweep** — 1px horizontal gradient line sweeps top to
bottom every 1525s at 0.06 opacity. Triggered via JS setTimeout.
### Interactive (JS + CSS transitions)
- **Scroll reveal**`.reveal` class, `IntersectionObserver` with
`threshold: 0.08`. Fade+translateY(12px→0), 0.6s ease-out. Above-fold
elements stagger with 80ms delay increments.
- **Cursor coordinates** — fixed `div` follows mouse, shows `x: # y: #`
in 7px mono, fades to 0 after 2s idle.
- **Icon hover coordinates** — CV-debug-style `x:### y:###` label at
top-right of icon cells, green color (`--color-success`), fades in
on hover. Icon SVGs scale 1.12x.
`threshold: 0.08`. Fade+translateY(12px→0), 0.6s ease-out.
Above-fold elements stagger with 80ms delay increments.
- **Cursor coordinates** — fixed `div` follows mouse, shows
`x: # y: #` in 7px mono, fades to 0 after 2s idle.
- **Node hover** — crosshair expansion + ring pulse + SLOT label +
connected edge highlighting (see Embedding Graph section).
- **Icon hover coordinates** — CV-debug-style `x:### y:###` label
at top-right of icon cells, green color (`--color-success`),
fades in on hover. Icon SVGs scale 1.12x.
- **Swatch hover** — scale 1.08x with 1px outline ring.
- **Input focus glow** — 1px `box-shadow` ring on focus.
### SVG Connector Overlay
JS-drawn SVG layer. On load (and resize), reads `getBoundingClientRect`
centers of schematic circles (`#sc-*`), crosshairs (`#ch-*`), and
star markers (`#sm-*`), then draws cubic bezier curves between them
with endpoint dots and slot/pass labels. This ensures connectors
always attach to real background elements. Edges defined as
`[fromId, toId, style, label?]` array. Uses `conn-dashed` /
`conn-dashed-rev` classes for animated `stroke-dashoffset`.
### Animation Anti-Patterns
- No bounce or spring animations
- No scale-up entrance effects
- No flash/blink effects
- No parallax scrolling
- Durations always > 5s for ambient, 0.20.6s for interactive
- Never `animation-iteration-count: 1` for ambient effects
## Layout Principles
@ -258,9 +358,11 @@ On toggle: update attribute, re-render all canvas dither art after
## Resources
- `assets/design-brief.png` — Full-page reference screenshot of
the design brief with all components and background layers.
- `assets/globals.css` — Complete token stylesheet with dual themes,
typography, components, icon classes, and utilities.
- `assets/index.html` — Full reference implementation with all
components, icon set, generative art, theme toggle.
components, icon set, generative art, embedding graph, theme toggle.
- `references/generative-patterns.md` — Perlin noise + fbm + Bayer
dither + glitch smear implementation with code.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -255,6 +255,7 @@
border: 1px solid var(--color-border-strong);
border-radius: 50%;
pointer-events: none;
opacity: 0.8;
animation: node-pulse 6s ease-in-out infinite;
}
.schematic-circle:nth-child(2) { animation-delay: -2s; animation-duration: 8s; }
@ -299,6 +300,8 @@
stroke: var(--color-border-strong);
stroke-width: 1;
fill: none;
opacity: 0.8;
transition: opacity 0.3s ease, stroke 0.3s ease, stroke-width 0.3s ease;
}
.connector-svg .conn-dashed {
stroke-dasharray: 6 4;
@ -317,6 +320,8 @@
.connector-svg .conn-dot {
fill: var(--color-text-muted);
r: 2;
opacity: 0.8;
transition: opacity 0.3s ease, fill 0.3s ease;
}
.connector-svg .conn-dot--bright {
fill: var(--color-text);
@ -334,6 +339,21 @@
text-transform: uppercase;
fill: var(--color-text-muted);
opacity: 0.4;
transition: opacity 0.3s ease, fill 0.3s ease;
}
/* Highlight state for edges connected to hovered node */
.connector-svg path.conn-highlight {
stroke: var(--color-text);
stroke-width: 1.5;
opacity: 1;
}
.connector-svg .conn-dot.conn-highlight {
fill: var(--color-text-bright);
opacity: 1;
}
.connector-svg .conn-label.conn-highlight {
fill: var(--color-text);
opacity: 0.8;
}
/* Vertical sidebar */
@ -639,12 +659,17 @@
position: absolute;
width: 12px;
height: 12px;
pointer-events: auto;
cursor: crosshair;
opacity: 0.8;
transition: opacity 0.3s ease;
}
.crosshair-field .ch::before,
.crosshair-field .ch::after {
content: '';
position: absolute;
background: var(--color-border);
transition: background 0.25s ease, transform 0.25s ease;
}
.crosshair-field .ch::before {
width: 1px;
@ -656,6 +681,55 @@
height: 1px;
top: 50%;
}
/* Node hover — expand crosshair, brighten */
.crosshair-field .ch:hover {
opacity: 1;
}
.crosshair-field .ch:hover::before {
background: var(--color-text);
transform: scaleY(1.8);
}
.crosshair-field .ch:hover::after {
background: var(--color-text);
transform: scaleX(1.8);
}
/* Node label — hidden until hover */
.crosshair-field .ch-label {
position: absolute;
top: -16px;
left: 50%;
transform: translateX(-50%);
font-family: var(--font-mono);
font-size: 6px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-text);
white-space: nowrap;
opacity: 0;
transition: opacity 0.25s ease;
pointer-events: none;
background: var(--color-bg);
padding: 1px 4px;
border: 1px solid var(--color-border-strong);
}
.crosshair-field .ch:hover .ch-label {
opacity: 1;
}
/* Ring pulse on hover */
.crosshair-field .ch-ring {
position: absolute;
inset: -6px;
border: 1px solid var(--color-text-muted);
border-radius: 50%;
opacity: 0;
transform: scale(0.6);
transition: opacity 0.3s ease, transform 0.3s ease;
pointer-events: none;
}
.crosshair-field .ch:hover .ch-ring {
opacity: 0.5;
transform: scale(1);
}
/* === Schematic overlay === */
.schematic-overlay {
@ -790,26 +864,26 @@
<!-- Graph nodes — positioned as embedding clusters -->
<div class="crosshair-field">
<!-- Cluster A: upper-left (near sc-1) -->
<div class="ch" id="ch-0" style="top: 6%; left: 12%;"></div>
<div class="ch" id="ch-1" style="top: 15%; left: 16%;"></div>
<div class="ch" id="ch-2" style="top: 10%; left: 22%;"></div>
<div class="ch" id="ch-3" style="top: 20%; left: 10%;"></div>
<div class="ch" id="ch-0" style="top: 6%; left: 12%;"><span class="ch-ring"></span><span class="ch-label">SLOT_00</span></div>
<div class="ch" id="ch-1" style="top: 15%; left: 16%;"><span class="ch-ring"></span><span class="ch-label">SLOT_01</span></div>
<div class="ch" id="ch-2" style="top: 10%; left: 22%;"><span class="ch-ring"></span><span class="ch-label">SLOT_02</span></div>
<div class="ch" id="ch-3" style="top: 20%; left: 10%;"><span class="ch-ring"></span><span class="ch-label">SLOT_03</span></div>
<!-- Cluster B: upper-right (sparse) -->
<div class="ch" id="ch-4" style="top: 8%; right: 10%;"></div>
<div class="ch" id="ch-5" style="top: 16%; right: 6%;"></div>
<div class="ch" id="ch-4" style="top: 8%; right: 10%;"><span class="ch-ring"></span><span class="ch-label">SLOT_04</span></div>
<div class="ch" id="ch-5" style="top: 16%; right: 6%;"><span class="ch-ring"></span><span class="ch-label">SLOT_05</span></div>
<!-- Cluster C: mid-left (near sc-2) -->
<div class="ch" id="ch-6" style="top: 45%; left: 12%;"></div>
<div class="ch" id="ch-7" style="top: 55%; left: 14%;"></div>
<div class="ch" id="ch-8" style="top: 48%; left: 20%;"></div>
<div class="ch" id="ch-6" style="top: 45%; left: 12%;"><span class="ch-ring"></span><span class="ch-label">SLOT_06</span></div>
<div class="ch" id="ch-7" style="top: 55%; left: 14%;"><span class="ch-ring"></span><span class="ch-label">SLOT_07</span></div>
<div class="ch" id="ch-8" style="top: 48%; left: 20%;"><span class="ch-ring"></span><span class="ch-label">SLOT_08</span></div>
<!-- Cluster D: lower region -->
<div class="ch" id="ch-9" style="top: 72%; left: 6%;"></div>
<div class="ch" id="ch-10" style="top: 78%; left: 12%;"></div>
<div class="ch" id="ch-11" style="bottom: 14%; right: 14%;"></div>
<div class="ch" id="ch-12" style="bottom: 22%; right: 10%;"></div>
<div class="ch" id="ch-9" style="top: 72%; left: 6%;"><span class="ch-ring"></span><span class="ch-label">SLOT_09</span></div>
<div class="ch" id="ch-10" style="top: 78%; left: 12%;"><span class="ch-ring"></span><span class="ch-label">SLOT_10</span></div>
<div class="ch" id="ch-11" style="bottom: 14%; right: 14%;"><span class="ch-ring"></span><span class="ch-label">SLOT_11</span></div>
<div class="ch" id="ch-12" style="bottom: 22%; right: 10%;"><span class="ch-ring"></span><span class="ch-label">SLOT_12</span></div>
<!-- Outliers -->
<div class="ch" id="ch-13" style="top: 35%; left: 4%;"></div>
<div class="ch" id="ch-14" style="top: 65%; right: 5%;"></div>
<div class="ch" id="ch-15" style="bottom: 8%; left: 8%;"></div>
<div class="ch" id="ch-13" style="top: 35%; left: 4%;"><span class="ch-ring"></span><span class="ch-label">SLOT_13</span></div>
<div class="ch" id="ch-14" style="top: 65%; right: 5%;"><span class="ch-ring"></span><span class="ch-label">SLOT_14</span></div>
<div class="ch" id="ch-15" style="bottom: 8%; left: 8%;"><span class="ch-ring"></span><span class="ch-label">SLOT_15</span></div>
</div>
<canvas id="dither-edge"></canvas>
@ -1544,16 +1618,14 @@
const a = elCenter(fromEl);
const b = elCenter(toEl);
// Curved path with control points offset perpendicular
// Single-curve quadratic bezier — one control point offset perpendicular
const dx = b.x - a.x;
const dy = b.y - a.y;
const cx1 = a.x + dx * 0.3 + dy * 0.15;
const cy1 = a.y + dy * 0.3 - dx * 0.15;
const cx2 = a.x + dx * 0.7 - dy * 0.15;
const cy2 = a.y + dy * 0.7 + dx * 0.15;
const mx = (a.x + b.x) / 2 + dy * 0.12;
const my = (a.y + b.y) / 2 - dx * 0.12;
const path = document.createElementNS(svgNS, 'path');
path.setAttribute('d', `M ${a.x},${a.y} C ${cx1},${cy1} ${cx2},${cy2} ${b.x},${b.y}`);
path.setAttribute('d', `M ${a.x},${a.y} Q ${mx},${my} ${b.x},${b.y}`);
path.classList.add(style === 'dashed-rev' ? 'conn-dashed-rev' : 'conn-dashed');
connSvg.appendChild(path);
@ -1583,6 +1655,49 @@
drawConnectors();
// === Node hover → highlight connected edges ===
// Build adjacency: nodeId → [edge indices]
const nodeEdges = {};
edges.forEach(([fromId, toId], i) => {
if (!nodeEdges[fromId]) nodeEdges[fromId] = [];
if (!nodeEdges[toId]) nodeEdges[toId] = [];
nodeEdges[fromId].push(i);
nodeEdges[toId].push(i);
});
// Each edge produces: 1 path + 2 dots + optional label = 3 or 4 SVG children
// We track which SVG children belong to which edge index
function getEdgeElements(edgeIdx) {
let childIdx = 0;
for (let i = 0; i < edges.length; i++) {
const pathEl = connSvg.children[childIdx++]; // path
const dot1 = connSvg.children[childIdx++]; // dot
const dot2 = connSvg.children[childIdx++]; // dot
let labelEl = null;
if (edges[i][3]) {
labelEl = connSvg.children[childIdx++]; // label
}
if (i === edgeIdx) return [pathEl, dot1, dot2, labelEl].filter(Boolean);
}
return [];
}
document.querySelectorAll('.crosshair-field .ch').forEach(ch => {
ch.addEventListener('mouseenter', () => {
const id = ch.id;
const indices = nodeEdges[id] || [];
for (const idx of indices) {
const els = getEdgeElements(idx);
els.forEach(el => el.classList.add('conn-highlight'));
}
});
ch.addEventListener('mouseleave', () => {
connSvg.querySelectorAll('.conn-highlight').forEach(el => {
el.classList.remove('conn-highlight');
});
});
});
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);