diff --git a/app.py b/app.py
index d8238c4..c7b4545 100644
--- a/app.py
+++ b/app.py
@@ -1545,6 +1545,10 @@ def get_filesystem_blocks():
zone_writing = int(round(writing_blocks_total * zone_write_share))
zone_writing = max(0, min(zone_used, zone_writing))
zone_writing_total += zone_writing
+ inode_pressure = int(max(0, min(
+ 100,
+ round((z["activity"] * 2.6) + (zone_writing * 0.9) + (local_used_ratio * 38.0))
+ )))
cell_index = 0
for r in range(row_start, row_end + 1):
@@ -1578,7 +1582,8 @@ def get_filesystem_blocks():
"row_end": row_end,
"activity": int(z["activity"]),
"used_percent": round(local_used_ratio * 100.0, 1),
- "writing_blocks": zone_writing
+ "writing_blocks": zone_writing,
+ "inode_pressure": inode_pressure
})
writing_blocks = sum(1 for b in blocks if b["state"] == "writing")
@@ -1586,6 +1591,10 @@ def get_filesystem_blocks():
FILESYSTEM_PREV["timestamp"] = now
FILESYSTEM_PREV["write_bytes"] = write_bytes
+ inode_pressure_global = 0
+ if zones:
+ inode_pressure_global = int(round(sum(int(z.get("inode_pressure", 0)) for z in zones) / len(zones)))
+
return {
"timestamp": datetime.now().isoformat(),
"rows": rows,
@@ -1598,7 +1607,8 @@ def get_filesystem_blocks():
"free_gb": free_gb,
"used_percent": round(used_percent, 2),
"write_bps": round(write_bps, 2),
- "writing_blocks": writing_blocks
+ "writing_blocks": writing_blocks,
+ "inode_pressure": inode_pressure_global
}
}
diff --git a/index.html b/index.html
index 5853c5c..2956112 100755
--- a/index.html
+++ b/index.html
@@ -100,8 +100,8 @@
Linux Kernel Ring 0 Visualization
-
-
+
+
diff --git a/static/js/crypto-belt.js b/static/js/crypto-belt.js
index 0a9377c..807dea1 100644
--- a/static/js/crypto-belt.js
+++ b/static/js/crypto-belt.js
@@ -1092,10 +1092,11 @@ class CryptoSubsystemVisualization {
const panelX = Math.floor(width * 0.41) + 10;
const maxSafeW = Math.max(300, rightColumnX - panelX - 16);
const panelW = Math.max(330, Math.min(Math.min(470, Math.floor(width * 0.3)), maxSafeW));
- const panelH = 238;
const decisionPanelY = Math.max(450, Math.floor(height * 0.52));
const materialCardH = 226;
- const panelY = Math.min(height - panelH - 22, decisionPanelY + materialCardH + 16);
+ const panelY = decisionPanelY + materialCardH + 16;
+ const maxPanelH = Math.max(120, height - panelY - 22);
+ const panelH = Math.min(238, maxPanelH);
const panel = layer.append('g').attr('class', 'crypto-entropy-cloud');
panel.append('rect')
@@ -1146,7 +1147,7 @@ class CryptoSubsystemVisualization {
const cloudY = panelY + 86;
// Keep particle viewport compact so right-side source metrics fit comfortably.
const cloudW = Math.max(130, panelW - 220);
- const cloudH = panelH - 100;
+ const cloudH = Math.max(60, panelH - 102);
panel.append('rect')
.attr('x', cloudX)
.attr('y', cloudY)
@@ -1178,7 +1179,8 @@ class CryptoSubsystemVisualization {
const keyRate = Number(entropy.key_birth_rate_est || 0);
const keyNodes = Math.max(1, Math.min(5, Math.round(keyRate / 2.2)));
const keyBaseX = panelX + panelW - 110;
- const keyBaseY = panelY + 112;
+ const srcY = panelY + panelH - 66;
+ const keyBaseY = srcY - 28;
panel.append('text')
.attr('x', keyBaseX)
.attr('y', keyBaseY - 12)
@@ -1210,7 +1212,6 @@ class CryptoSubsystemVisualization {
.style('stroke-opacity', 0.7);
const srcX = panelX + panelW - 158;
- const srcY = panelY + 152;
const sourceBarX = srcX + 70;
const sourceBarW = Math.max(64, Math.min(76, panelX + panelW - sourceBarX - 8));
panel.append('text')
@@ -1274,8 +1275,9 @@ class CryptoSubsystemVisualization {
const queueH = 86;
const offloadH = 116;
const totalH = clientsH + queueH + offloadH + (gap * 2);
- // Keep stage-1 HUD fully visible even on shorter viewports.
- const baseY = Math.max(180, height - totalH - 20);
+ // Align left stage stack with Algorithm Material Card top level.
+ const decisionPanelY = Math.max(450, Math.floor(height * 0.52));
+ const baseY = Math.min(decisionPanelY, Math.max(180, height - totalH - 20));
const isAllSelected = this.selectedClientFilters.size === 0;
const drawPanelShell = (x, y, w, h, title) => {
@@ -1489,9 +1491,9 @@ class CryptoSubsystemVisualization {
}
drawNode(group, x, y, label, level, intensity, palette, emphasis) {
- const width = Math.min(Math.max(150, String(label).length * 8 + 28), 250);
- const height = 34;
- const radius = 8;
+ const width = Math.min(Math.max(132, String(label).length * 7 + 24), 220);
+ const height = 30;
+ const radius = 7;
const lineColor = emphasis ? palette.accent : (intensity > 1.2 ? palette.accent : palette.stroke);
const fillColor = level === 'crypto' ? '#11161f' : palette.fill;
@@ -1513,7 +1515,7 @@ class CryptoSubsystemVisualization {
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.style('font-family', 'Share Tech Mono, monospace')
- .style('font-size', '11px')
+ .style('font-size', '10px')
.style('letter-spacing', '0.35px')
.style('fill', emphasis ? '#ffffff' : '#eef3fb')
.text(String(label).toUpperCase());
diff --git a/static/js/filesystem-map.js b/static/js/filesystem-map.js
index 0e6ff33..8dc09a5 100644
--- a/static/js/filesystem-map.js
+++ b/static/js/filesystem-map.js
@@ -1,7 +1,7 @@
// Filesystem Block Map Visualization
-// Version: 2
+// Version: 3
-debugLog('🗂️ filesystem-map.js v2: Script loading...');
+debugLog('🗂️ filesystem-map.js v3: Script loading...');
class FilesystemMapVisualization {
constructor() {
@@ -15,6 +15,12 @@ class FilesystemMapVisualization {
this.overlayNodes = [];
this.resizeHandler = null;
this.telemetry = null;
+ this.tick = 0;
+ this.renderMode = 'occupancy';
+ this.modeButtons = new Map();
+ this.legendNode = null;
+ this.orbHitArea = null;
+ this.canvasClickHandler = null;
}
init(containerId = 'filesystem-map-container') {
@@ -29,7 +35,7 @@ class FilesystemMapVisualization {
inset: 0;
width: 100%;
height: 100%;
- background: #0E1114;
+ background: #090d14;
z-index: 9999;
display: none;
visibility: hidden;
@@ -48,6 +54,8 @@ class FilesystemMapVisualization {
`;
this.container.appendChild(this.canvas);
this.ctx = this.canvas.getContext('2d');
+ this.canvasClickHandler = (event) => this.onCanvasClick(event);
+ this.canvas.addEventListener('click', this.canvasClickHandler);
this.createOverlayUI();
this.addExitButton();
@@ -71,7 +79,7 @@ class FilesystemMapVisualization {
letter-spacing: 1px;
z-index: 1001;
`;
- title.textContent = 'FILESYSTEM BLOCK MAP';
+ title.textContent = 'FILESYSTEM INDEX ARCHIVE (in development)';
this.container.appendChild(title);
this.overlayNodes.push(title);
@@ -86,9 +94,46 @@ class FilesystemMapVisualization {
font-size: 11px;
z-index: 1001;
`;
- legend.textContent = 'filesystem map by zones: gray=free, black=used, yellow=actively writing';
+ legend.textContent = 'filesystem packages + radial block index: blue=used, cyan=writing, dark=free';
this.container.appendChild(legend);
this.overlayNodes.push(legend);
+ this.legendNode = legend;
+
+ const modePanel = document.createElement('div');
+ modePanel.style.cssText = `
+ position: absolute;
+ top: 72px;
+ right: 22px;
+ display: flex;
+ gap: 6px;
+ z-index: 1001;
+ `;
+ const modes = [
+ { key: 'occupancy', label: 'OCCUPANCY' },
+ { key: 'write', label: 'WRITE HEAT' },
+ { key: 'inode', label: 'INODE' }
+ ];
+ modes.forEach((item) => {
+ const btn = document.createElement('button');
+ btn.textContent = item.label;
+ btn.style.cssText = `
+ padding: 4px 8px;
+ background: rgba(12, 18, 28, 0.9);
+ border: 1px solid rgba(130, 148, 172, 0.35);
+ color: #9fb0c8;
+ font-family: 'Share Tech Mono', monospace;
+ font-size: 9px;
+ letter-spacing: 0.3px;
+ cursor: pointer;
+ `;
+ btn.onclick = () => this.setRenderMode(item.key);
+ modePanel.appendChild(btn);
+ this.modeButtons.set(item.key, btn);
+ this.overlayNodes.push(btn);
+ });
+ this.container.appendChild(modePanel);
+ this.overlayNodes.push(modePanel);
+ this.setRenderMode(this.renderMode);
const info = document.createElement('div');
info.style.cssText = `
@@ -161,7 +206,8 @@ class FilesystemMapVisualization {
`used ${m.used_gb || 0}GB / ${m.total_gb || 0}GB (${m.used_percent || 0}%)` +
` | free ${m.free_gb || 0}GB` +
` | write ${(m.write_bps || 0).toFixed(0)} B/s` +
- ` | active blocks ${m.writing_blocks || 0}`;
+ ` | writing blocks ${m.writing_blocks || 0}` +
+ ` | inode ${m.inode_pressure || 0}%`;
}
})
.catch((err) => {
@@ -171,13 +217,129 @@ class FilesystemMapVisualization {
});
}
- drawGrid() {
+ drawRoundedRect(x, y, w, h, r) {
+ const ctx = this.ctx;
+ const radius = Math.max(0, Math.min(r, w / 2, h / 2));
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ ctx.lineTo(x + w - radius, y);
+ ctx.quadraticCurveTo(x + w, y, x + w, y + radius);
+ ctx.lineTo(x + w, y + h - radius);
+ ctx.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
+ ctx.lineTo(x + radius, y + h);
+ ctx.quadraticCurveTo(x, y + h, x, y + h - radius);
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+ }
+
+ drawPackageStack(x, y, w, h, layers, accentColor, label, metaText) {
+ const ctx = this.ctx;
+ const stackLayers = Math.max(2, Math.min(4, layers));
+ for (let i = stackLayers - 1; i >= 0; i -= 1) {
+ const dx = -i * 4;
+ const dy = i * 3;
+ this.drawRoundedRect(x + dx, y + dy, w, h, 5);
+ ctx.fillStyle = i === 0 ? 'rgba(14, 24, 36, 0.95)' : 'rgba(10, 16, 26, 0.85)';
+ ctx.fill();
+ ctx.strokeStyle = i === 0 ? accentColor : 'rgba(72, 94, 124, 0.35)';
+ ctx.lineWidth = i === 0 ? 1.0 : 0.7;
+ ctx.stroke();
+ }
+ ctx.fillStyle = '#9fc0e8';
+ ctx.font = '9px "Share Tech Mono", monospace';
+ ctx.fillText(label, x + 8, y + 13);
+ ctx.fillStyle = 'rgba(122, 141, 167, 0.85)';
+ ctx.font = '8px "Share Tech Mono", monospace';
+ ctx.fillText(metaText, x + 8, y + 24);
+ }
+
+ drawParticleOrb(x, y, radius, meta) {
+ const ctx = this.ctx;
+ const mode = String(this.renderMode || 'occupancy');
+ const inodePressure = Math.max(0, Math.min(100, Number(meta?.inode_pressure || 0)));
+ const writeBps = Math.max(0, Number(meta?.write_bps || 0));
+ const writeNorm = Math.max(0, Math.min(1, writeBps / (300 * 1024 * 1024)));
+ const modeBoost = mode === 'write' ? 0.25 : (mode === 'inode' ? 0.18 : 0.08);
+ const pulse = 0.45 + 0.55 * ((Math.sin(this.tick * 0.06) + 1) / 2);
+ const glow = Math.max(0.1, Math.min(1.0, (inodePressure / 100) * 0.6 + writeNorm * 0.4 + modeBoost));
+
+ // Outer glow
+ const g = ctx.createRadialGradient(x, y, radius * 0.3, x, y, radius * 1.2);
+ g.addColorStop(0, `rgba(145, 222, 255, ${0.12 + glow * 0.25})`);
+ g.addColorStop(1, 'rgba(145, 222, 255, 0)');
+ ctx.beginPath();
+ ctx.arc(x, y, radius * 1.2, 0, Math.PI * 2);
+ ctx.fillStyle = g;
+ ctx.fill();
+
+ // Orb body
+ const orb = ctx.createRadialGradient(x - radius * 0.25, y - radius * 0.35, radius * 0.2, x, y, radius);
+ orb.addColorStop(0, 'rgba(196, 238, 255, 0.85)');
+ orb.addColorStop(0.45, 'rgba(79, 148, 196, 0.46)');
+ orb.addColorStop(1, 'rgba(11, 22, 36, 0.95)');
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
+ ctx.fillStyle = orb;
+ ctx.fill();
+
+ // Rim
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
+ ctx.strokeStyle = `rgba(137, 208, 245, ${0.58 + pulse * 0.22})`;
+ ctx.lineWidth = 1.2;
+ ctx.stroke();
+
+ // Minimal version: no inner streaks and no labels.
+ }
+
+ setRenderMode(modeKey) {
+ this.renderMode = ['occupancy', 'write', 'inode'].includes(modeKey) ? modeKey : 'occupancy';
+ this.modeButtons.forEach((btn, key) => {
+ const active = key === this.renderMode;
+ btn.style.background = active ? 'rgba(34, 58, 88, 0.92)' : 'rgba(12, 18, 28, 0.9)';
+ btn.style.borderColor = active ? 'rgba(129, 180, 255, 0.9)' : 'rgba(130, 148, 172, 0.35)';
+ btn.style.color = active ? '#d3e7ff' : '#9fb0c8';
+ });
+ if (this.legendNode) {
+ if (this.renderMode === 'write') {
+ this.legendNode.textContent = 'mode: write heat (cyan=write hot, blue=active, dark=cold)';
+ } else if (this.renderMode === 'inode') {
+ this.legendNode.textContent = 'mode: inode pressure (cyan=high inode pressure)';
+ } else {
+ this.legendNode.textContent = 'mode: occupancy (blue=used, cyan=writing, dark=free)';
+ }
+ }
+ }
+
+ cycleRenderMode() {
+ const order = ['occupancy', 'write', 'inode'];
+ const idx = order.indexOf(this.renderMode);
+ const next = order[(idx + 1) % order.length];
+ this.setRenderMode(next);
+ }
+
+ onCanvasClick(event) {
+ if (!this.orbHitArea || !this.canvas) return;
+ const rect = this.canvas.getBoundingClientRect();
+ const x = event.clientX - rect.left;
+ const y = event.clientY - rect.top;
+ const dx = x - this.orbHitArea.x;
+ const dy = y - this.orbHitArea.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist <= this.orbHitArea.r * 1.25) {
+ this.cycleRenderMode();
+ }
+ }
+
+ drawScene() {
if (!this.ctx || !this.canvas) return;
const w = window.innerWidth;
const h = window.innerHeight;
this.ctx.clearRect(0, 0, w, h);
+ this.tick += 1;
- this.ctx.fillStyle = '#0E1114';
+ this.ctx.fillStyle = '#090d14';
this.ctx.fillRect(0, 0, w, h);
if (!this.telemetry || !Array.isArray(this.telemetry.blocks)) return;
@@ -186,56 +348,162 @@ class FilesystemMapVisualization {
const cols = Math.max(1, Number(this.telemetry.cols || 1));
const blocks = this.telemetry.blocks;
const zones = Array.isArray(this.telemetry.zones) ? this.telemetry.zones : [];
+ const zoneMap = new Map(zones.map((z) => [String(z.id || ''), z]));
- const topPad = 120;
- const bottomPad = 40;
- const sidePad = 70;
- const availableW = Math.max(100, w - sidePad * 2);
- const availableH = Math.max(100, h - topPad - bottomPad);
+ const cx = Math.floor(w * 0.53);
+ const cy = Math.floor(h * 0.63);
+ const outerR = Math.max(130, Math.min(250, Math.floor(Math.min(w, h) * 0.28)));
+ const innerR = Math.max(24, Math.floor(outerR * 0.14));
+ const ringGap = (outerR - innerR) / Math.max(1, rows);
+ const angleStep = (Math.PI * 2) / Math.max(1, cols);
- const cellW = Math.floor(availableW / cols);
- const cellH = Math.floor(availableH / rows);
- const size = Math.max(4, Math.min(cellW, cellH) - 2);
- const startX = Math.floor((w - (cols * (size + 2))) / 2);
- const startY = Math.floor(topPad + (availableH - (rows * (size + 2))) / 2);
+ // Background circuit lines.
+ this.ctx.strokeStyle = 'rgba(150, 38, 64, 0.17)';
+ this.ctx.lineWidth = 1;
+ for (let i = 0; i < 9; i += 1) {
+ const y = 120 + i * 44;
+ this.ctx.beginPath();
+ this.ctx.moveTo(cx - outerR - 130, y);
+ this.ctx.lineTo(cx + outerR + 130, y);
+ this.ctx.stroke();
+ }
+ // Main rings.
+ for (let i = 0; i <= 7; i += 1) {
+ const r = innerR + ((outerR - innerR) * i / 7);
+ this.ctx.beginPath();
+ this.ctx.arc(cx, cy, r, 0, Math.PI * 2);
+ this.ctx.strokeStyle = i % 2 === 0 ? 'rgba(89, 140, 192, 0.22)' : 'rgba(58, 96, 136, 0.16)';
+ this.ctx.lineWidth = i === 7 ? 1.2 : 0.8;
+ this.ctx.stroke();
+ }
+
+ // Radial block matrix.
blocks.forEach((b) => {
- const r = Number.isFinite(Number(b.r)) ? Number(b.r) : Math.floor(Number(b.i || 0) / cols);
- const c = Number.isFinite(Number(b.c)) ? Number(b.c) : (Number(b.i || 0) % cols);
- const x = startX + c * (size + 2);
- const y = startY + r * (size + 2);
+ const rIdx = Number.isFinite(Number(b.r)) ? Number(b.r) : Math.floor(Number(b.i || 0) / cols);
+ const cIdx = Number.isFinite(Number(b.c)) ? Number(b.c) : (Number(b.i || 0) % cols);
+ const r0 = innerR + rIdx * ringGap;
+ const r1 = r0 + Math.max(1.3, ringGap * 0.8);
+ const a0 = cIdx * angleStep;
+ const a1 = a0 + angleStep * 0.88;
- if (b.state === 'free') this.ctx.fillStyle = '#8f9499';
- else if (b.state === 'writing') this.ctx.fillStyle = '#e6c15a';
- else this.ctx.fillStyle = '#15171a';
+ const z = zoneMap.get(String(b.zone_id || '')) || {};
+ const zoneAct = Math.max(0, Math.min(100, Number(z.activity || 0)));
+ const inodePressure = Math.max(0, Math.min(100, Number(z.inode_pressure || 0)));
+ if (this.renderMode === 'write') {
+ if (b.state === 'writing') this.ctx.fillStyle = 'rgba(127, 232, 255, 0.92)';
+ else if (b.state === 'used') this.ctx.fillStyle = `rgba(44, 132, 184, ${0.32 + Math.min(0.5, zoneAct / 120)})`;
+ else this.ctx.fillStyle = 'rgba(14, 28, 44, 0.48)';
+ } else if (this.renderMode === 'inode') {
+ if (b.state === 'free') this.ctx.fillStyle = 'rgba(12, 24, 38, 0.45)';
+ else this.ctx.fillStyle = `rgba(127, 232, 255, ${0.18 + Math.min(0.72, inodePressure / 110)})`;
+ } else {
+ if (b.state === 'writing') this.ctx.fillStyle = 'rgba(127, 232, 255, 0.9)';
+ else if (b.state === 'free') this.ctx.fillStyle = 'rgba(19, 38, 58, 0.55)';
+ else this.ctx.fillStyle = 'rgba(44, 132, 184, 0.72)';
+ }
- this.ctx.fillRect(x, y, size, size);
+ this.ctx.beginPath();
+ this.ctx.arc(cx, cy, r1, a0, a1);
+ this.ctx.arc(cx, cy, r0, a1, a0, true);
+ this.ctx.closePath();
+ this.ctx.fill();
});
- // Draw zone boundaries and labels to make a readable filesystem map.
- this.ctx.font = '11px "Share Tech Mono", monospace';
- zones.forEach((z) => {
- const rs = Number(z.row_start || 0);
- const re = Number(z.row_end || rs);
- const zx = startX - 6;
- const zy = startY + rs * (size + 2) - 4;
- const zw = cols * (size + 2) + 8;
- const zh = (re - rs + 1) * (size + 2) + 6;
-
- this.ctx.strokeStyle = 'rgba(170, 178, 188, 0.26)';
+ // Central hole.
+ this.ctx.beginPath();
+ this.ctx.arc(cx, cy, innerR - 3, 0, Math.PI * 2);
+ this.ctx.fillStyle = '#081018';
+ this.ctx.fill();
+
+ // Alert banner.
+ const m = this.telemetry.meta || {};
+ if (Number(m.writing_blocks || 0) > 0) {
+ const pulse = 0.5 + 0.5 * Math.sin(this.tick * 0.08);
+ const bw = 330;
+ const bh = 40;
+ const bx = cx - Math.floor(bw / 2);
+ const by = cy - outerR - 26;
+ this.drawRoundedRect(bx, by, bw, bh, 5);
+ this.ctx.fillStyle = `rgba(${90 + Math.floor(80 * pulse)}, 20, 20, 0.9)`;
+ this.ctx.fill();
+ this.ctx.strokeStyle = 'rgba(220, 90, 90, 0.85)';
this.ctx.lineWidth = 1;
- this.ctx.strokeRect(zx, zy, zw, zh);
+ this.ctx.stroke();
+ this.ctx.fillStyle = '#ffd8c0';
+ this.ctx.font = 'bold 17px "Share Tech Mono", monospace';
+ this.ctx.fillText('DATA PACKAGE WRITING', bx + 18, by + 26);
+ }
- const label = `${z.name} used:${z.used_percent}% act:${z.activity}`;
- this.ctx.fillStyle = 'rgba(200, 206, 216, 0.92)';
- this.ctx.fillText(label, zx + 8, zy + 14);
- });
+ // Side package stacks from zones.
+ const zoneItems = zones.slice(0, 14);
+ const leftItems = zoneItems.filter((_, i) => i % 2 === 0);
+ const rightItems = zoneItems.filter((_, i) => i % 2 === 1);
+ const cardW = 90;
+ const cardH = 32;
+ const leftX = Math.max(24, cx - outerR - 220);
+ const rightX = Math.min(w - cardW - 24, cx + outerR + 110);
+ const topY = Math.max(150, cy - outerR + 12);
+
+ const drawZonePack = (list, xBase, isLeft) => {
+ list.forEach((z, idx) => {
+ const y = topY + idx * 42;
+ const active = Number(z.activity || 0) > 6 || Number(z.writing_blocks || 0) > 0;
+ const accent = active ? 'rgba(118, 220, 255, 0.92)' : 'rgba(90, 132, 178, 0.7)';
+ const layers = 2 + (active ? 2 : 1);
+ this.drawPackageStack(
+ xBase,
+ y,
+ cardW,
+ cardH,
+ layers,
+ accent,
+ String(z.name || z.id || 'zone'),
+ `used:${Number(z.used_percent || 0).toFixed(0)}%`
+ );
+
+ // Connector to central disk.
+ const sx = isLeft ? xBase + cardW : xBase;
+ const sy = y + cardH / 2;
+ const tx = cx + (isLeft ? -outerR * 0.85 : outerR * 0.85);
+ const ty = cy - outerR * 0.65 + idx * 10;
+ this.ctx.beginPath();
+ this.ctx.moveTo(sx, sy);
+ this.ctx.bezierCurveTo(
+ sx + (isLeft ? 60 : -60),
+ sy,
+ tx + (isLeft ? -40 : 40),
+ ty,
+ tx,
+ ty
+ );
+ this.ctx.strokeStyle = 'rgba(169, 52, 80, 0.26)';
+ this.ctx.lineWidth = 1;
+ this.ctx.stroke();
+ });
+ };
+
+ drawZonePack(leftItems, leftX, true);
+ drawZonePack(rightItems, rightX, false);
+
+ // Mini particle-view orb (reference-style status widget).
+ const orbX = Math.max(84, leftX + 44);
+ const orbY = Math.min(h - 48, topY + 290);
+ this.drawParticleOrb(orbX, orbY, 42, m);
+ this.orbHitArea = { x: orbX, y: orbY, r: 42 };
+
+ // Minimal host-data labels.
+ this.ctx.fillStyle = '#6fa4cf';
+ this.ctx.font = '10px "Share Tech Mono", monospace';
+ this.ctx.fillText(`HOST/DATA FILE ${(m.used_gb || 0).toFixed(0)}GB`, leftX, topY - 26);
+ this.ctx.fillText(`HOST/DATA FILE ${(m.free_gb || 0).toFixed(0)}GB`, leftX, topY - 10);
+ this.ctx.fillText(`HOST/DATA WR ${(m.write_bps || 0).toFixed(0)} B/s`, rightX - 40, topY - 10);
}
animate() {
if (!this.isActive) return;
this.animationId = requestAnimationFrame(() => this.animate());
- this.drawGrid();
+ this.drawScene();
}
activate() {
diff --git a/templates/linux-crypto-subsystem.html b/templates/linux-crypto-subsystem.html
index 9ea400f..582c756 100644
--- a/templates/linux-crypto-subsystem.html
+++ b/templates/linux-crypto-subsystem.html
@@ -135,7 +135,7 @@ Linux Crypto Kernel Visualization
-
+