nothumanallowed 13.2.93 → 13.2.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "13.2.93",
3
+ "version": "13.2.94",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '13.2.93';
8
+ export const VERSION = '13.2.94';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -3677,7 +3677,8 @@ function downloadStudioPDF() {
3677
3677
  '</div>' +
3678
3678
  '</body></html>';
3679
3679
 
3680
- // Generate PDF using jsPDF + html2canvas (loaded from CDN on demand)
3680
+ // Generate PDF injects print-safe CSS then uses html2canvas at 2.5x for crisp output.
3681
+ // Page breaks are avoided inside .section/.card/.priority-item by injecting break-inside:avoid.
3681
3682
  var pdfFileName = (studioState.task || 'NHA Studio Report').slice(0, 60).replace(/[^a-z0-9\s]/gi,'').trim().replace(/\s+/g,'-') + '.pdf';
3682
3683
  function doGeneratePdf() {
3683
3684
  var btn2 = document.getElementById('studioInlinePdfBtn');
@@ -3687,33 +3688,67 @@ function downloadStudioPDF() {
3687
3688
  if (dlBtn2) { dlBtn2.disabled = b; dlBtn2.textContent = b ? 'Generando PDF...' : '\u2913 Download PDF'; }
3688
3689
  }
3689
3690
  setBusy(true);
3690
- // Build iframe to render the report HTML at full width
3691
+
3692
+ // Inject page-break-safe CSS into the HTML before rendering
3693
+ var printCss = '<style>body{padding:20px!important;max-width:800px!important;margin:0 auto!important}' +
3694
+ '.section,.card,.priority-item,.source-item,.bar-row{break-inside:avoid;page-break-inside:avoid}' +
3695
+ '.header{break-after:avoid;page-break-after:avoid}' +
3696
+ 'h1,h2,h3,h4{break-after:avoid;page-break-after:avoid}' +
3697
+ '@media print{body{background:#fff!important;color:#111!important}' +
3698
+ '.header{background:linear-gradient(135deg,#4f46e5,#06b6d4)!important;-webkit-print-color-adjust:exact}' +
3699
+ '.section{background:#f8f9fa!important;border:1px solid #e0e0e0!important}' +
3700
+ '.section-title{color:#4f46e5!important}}</style>';
3701
+ var pdfHtml = html.replace('</head>', printCss + '</head>');
3702
+
3703
+ // Build hidden iframe at 800px (A4 equivalent at 96dpi)
3691
3704
  var iframe = document.createElement('iframe');
3692
- iframe.style.cssText = 'position:fixed;left:-9999px;top:0;width:900px;height:1px;border:none;visibility:hidden';
3705
+ iframe.style.cssText = 'position:fixed;left:-9999px;top:0;width:800px;height:1px;border:none;visibility:hidden';
3693
3706
  document.body.appendChild(iframe);
3694
3707
  var ifrDoc = iframe.contentDocument || iframe.contentWindow.document;
3695
- ifrDoc.open(); ifrDoc.write(html); ifrDoc.close();
3708
+ ifrDoc.open(); ifrDoc.write(pdfHtml); ifrDoc.close();
3696
3709
  iframe.onload = function() {
3697
3710
  var body = ifrDoc.body;
3698
3711
  var totalH = Math.max(body.scrollHeight, body.offsetHeight, ifrDoc.documentElement.scrollHeight);
3699
3712
  iframe.style.height = totalH + 'px';
3713
+ // Render at 2.5x scale for crisp text
3700
3714
  window.html2canvas(body, {
3701
- scale: 1.5, useCORS: true, allowTaint: true,
3702
- width: 900, windowWidth: 900,
3715
+ scale: 2.5, useCORS: true, allowTaint: true,
3716
+ backgroundColor: '#0d0d14',
3717
+ width: 800, windowWidth: 800,
3703
3718
  scrollX: 0, scrollY: 0,
3704
3719
  ignoreElements: function(el){ return el.tagName === 'SCRIPT'; }
3705
3720
  }).then(function(canvas) {
3706
- var imgData = canvas.toDataURL('image/jpeg', 0.85);
3721
+ var imgData = canvas.toDataURL('image/png'); // PNG for crisp text
3707
3722
  var pdf = new window.jspdf.jsPDF({orientation:'portrait', unit:'pt', format:'a4', compress:true});
3708
- var pageW = pdf.internal.pageSize.getWidth();
3709
- var pageH = pdf.internal.pageSize.getHeight();
3710
- var imgW = pageW;
3711
- var imgH = (canvas.height * pageW) / canvas.width;
3712
- var y = 0;
3713
- while (y < imgH) {
3714
- if (y > 0) pdf.addPage();
3715
- pdf.addImage(imgData, 'JPEG', 0, -y, imgW, imgH, '', 'FAST');
3716
- y += pageH;
3723
+ var pageW = pdf.internal.pageSize.getWidth(); // 595pt
3724
+ var pageH = pdf.internal.pageSize.getHeight(); // 842pt
3725
+ var margin = 24; // pt
3726
+ var usableW = pageW - margin * 2;
3727
+ var usableH = pageH - margin * 2;
3728
+ // Scale image to fit page width
3729
+ var imgW = usableW;
3730
+ var imgH = (canvas.height * usableW) / canvas.width;
3731
+
3732
+ // Smart paging: find safe cut points every ~usableH
3733
+ // Strategy: render slice by slice, each slice fits one page
3734
+ var sliceH = Math.floor(canvas.height * (usableH / imgH)); // canvas px per page
3735
+ var yCanvas = 0;
3736
+ var pageNum = 0;
3737
+ while (yCanvas < canvas.height) {
3738
+ if (pageNum > 0) pdf.addPage();
3739
+ var remaining = canvas.height - yCanvas;
3740
+ var thisSlice = Math.min(sliceH, remaining);
3741
+ // Create a temporary canvas for this slice
3742
+ var sliceCanvas = document.createElement('canvas');
3743
+ sliceCanvas.width = canvas.width;
3744
+ sliceCanvas.height = thisSlice;
3745
+ var ctx = sliceCanvas.getContext('2d');
3746
+ ctx.drawImage(canvas, 0, yCanvas, canvas.width, thisSlice, 0, 0, canvas.width, thisSlice);
3747
+ var sliceData = sliceCanvas.toDataURL('image/png');
3748
+ var sliceImgH = (thisSlice * usableW) / canvas.width;
3749
+ pdf.addImage(sliceData, 'PNG', margin, margin, usableW, sliceImgH, '', 'FAST');
3750
+ yCanvas += thisSlice;
3751
+ pageNum++;
3717
3752
  }
3718
3753
  pdf.save(pdfFileName);
3719
3754
  document.body.removeChild(iframe);
@@ -3803,6 +3838,8 @@ async function runStudio() {
3803
3838
  studioState.running = true;
3804
3839
  studioState.planned = false;
3805
3840
  // Keep attachmentContext — it was loaded before hitting Run
3841
+ var parlBlockEl = document.getElementById('studioParliamentBlock');
3842
+ if (parlBlockEl) parlBlockEl.style.display = 'none';
3806
3843
  renderStudioNodes();
3807
3844
  renderStudioLog();
3808
3845
  renderStudioResult();
@@ -3929,6 +3966,38 @@ async function runStudio() {
3929
3966
  var parlNodeIdx = studioState.nodes.length;
3930
3967
  studioState.nodes.push({icon:\x27&#x2656;\x27, agent:\x27Parliament\x27, label:\x27Parlamento\x27, status:\x27running\x27, output:\x27\x27, _rendered:false});
3931
3968
  renderStudioNodes();
3969
+
3970
+ // ── Parliament visual block ──────────────────────────────────────
3971
+ // Track active R2 agent for visual block
3972
+ var parlActiveAgent = null;
3973
+ var parlDoneAgents = {};
3974
+
3975
+ function renderParlBlock(phase, activeLabel, convergence) {
3976
+ var pb = document.getElementById(\x27studioParliamentBlock\x27);
3977
+ if (!pb) return;
3978
+ pb.style.display = \x27block\x27;
3979
+ var agentCircles = proposals.map(function(p) {
3980
+ var lbl = p.label || p.agent;
3981
+ var isDone = !!parlDoneAgents[lbl];
3982
+ var isActive = lbl === activeLabel;
3983
+ var circCls = \x27studio-parliament__agent-circle\x27 + (isActive ? \x27 studio-parliament__agent-circle--reading\x27 : isDone ? \x27 studio-parliament__agent-circle--done\x27 : \x27\x27);
3984
+ var readingTxt = isActive
3985
+ ? (\x27<div class="studio-parliament__reading">\u21c4 legge ' + proposals.filter(function(x){return (x.label||x.agent)!==lbl;}).map(function(x){return x.label||x.agent;}).slice(0,2).join(\x27, \x27) + \x27</div>\x27)
3986
+ : \x27\x27;
3987
+ return \x27<div class="studio-parliament__agent"><div class="\x27 + circCls + \x27">\x27 + esc(p.icon||String.fromCharCode(9632)) + \x27</div><div class="studio-parliament__agent-label">\x27 + esc(lbl.slice(0,14)) + \x27</div>\x27 + readingTxt + \x27</div>\x27;
3988
+ }).join(\x27<div class="studio-parliament__bidir">\u21c4</div>\x27);
3989
+
3990
+ var phaseLabel = phase === \x27r1\x27 ? \x27Round 1\x27 : phase === \x27r2\x27 ? \x27Round 2 — Cross-reading\x27 : phase === \x27r3\x27 ? \x27Round 3 — Mediazione\x27 : \x27Completato\x27;
3991
+ var roundEl = convergence != null
3992
+ ? \x27<div class="studio-parliament__convergence">&#x2714; Convergenza: \x27 + convergence + \x27% &mdash; deliberazione completata</div>\x27
3993
+ : \x27<span class="studio-parliament__round">\x27 + phaseLabel + \x27</span>\x27;
3994
+
3995
+ pb.innerHTML = \x27<div class="studio-parliament"><div class="studio-parliament__header"><div class="studio-parliament__master"><span class="studio-parliament__master-icon">&#x2666;</span><span>MASTER AGENT &mdash; Orchestrazione</span></div><div class="studio-parliament__title">&#x2656; PARLIAMENT</div>\x27 + (convergence == null ? roundEl : \x27\x27) + \x27</div><div class="studio-parliament__agents">\x27 + agentCircles + \x27</div>\x27 + (convergence != null ? roundEl : \x27\x27) + \x27</div>\x27;
3996
+ }
3997
+
3998
+ // Show initial R1 block
3999
+ renderParlBlock(\x27r1\x27, null, null);
4000
+
3932
4001
  var deliberateBody = JSON.stringify({task: task, proposals: proposals, language: document.getElementById(\x27langSelect\x27) ? document.getElementById(\x27langSelect\x27).value : \x27it\x27});
3933
4002
  try {
3934
4003
  var delRes = await fetch(\x27/api/studio/deliberate\x27, {method:\x27POST\x27, headers:{\x27Content-Type\x27:\x27application/json\x27}, body: deliberateBody, signal: studioAbortController ? studioAbortController.signal : undefined});
@@ -3950,12 +4019,11 @@ async function runStudio() {
3950
4019
  try {
3951
4020
  var dev = JSON.parse(dd);
3952
4021
  if (dev.token) {
3953
- // Status tokens — check for Round 2 start to add new animated log entry
3954
4022
  var r2StartM = dev.token.match(/^\\[Round 2: (.+?)\\]\\s*$/);
3955
4023
  var r2LiveM = dev.token.match(/^\\[Round 2 (.+?): (\\d+) token\\]\\s*$/);
3956
4024
  if (r2StartM) {
3957
- // New R2 agent starting — add a new log entry with thinking animation
3958
4025
  var r2Label = r2StartM[1];
4026
+ parlActiveAgent = r2Label;
3959
4027
  studioLog(r2Label, \x27&#x2656;\x27, \x27\x27, \x27agent\x27, false);
3960
4028
  var delEnts2 = document.querySelectorAll(\x27.studio-log-entry\x27);
3961
4029
  var delL2 = delEnts2[delEnts2.length - 1];
@@ -3964,7 +4032,7 @@ async function runStudio() {
3964
4032
  var delTb2 = delL2.querySelector(\x27.studio-log-entry__text\x27);
3965
4033
  if (delTb2) delTb2.innerHTML = \x27<span style="color:var(--green);font-family:var(--mono);font-size:10px">&#x2656; Deliberando Round 2<span class="thinking-dots"><span></span><span></span><span></span></span></span>\x27;
3966
4034
  }
3967
- // Update Parliament pipeline node — show R2 agent + who it reads
4035
+ renderParlBlock(\x27r2\x27, r2Label, null);
3968
4036
  if (studioState.nodes[parlNodeIdx]) {
3969
4037
  var otherLabels = proposals.filter(function(p){ return (p.label || p.agent) !== r2Label; }).map(function(p){ return p.label || p.agent; });
3970
4038
  var readingStr = otherLabels.slice(0,2).join(\x27 + \x27) + (otherLabels.length > 2 ? \x27 +\x27 + (otherLabels.length-2) : \x27\x27);
@@ -3973,7 +4041,6 @@ async function runStudio() {
3973
4041
  renderStudioNodes();
3974
4042
  }
3975
4043
  } else if (r2LiveM) {
3976
- // Live token count update for the current R2 agent
3977
4044
  var r2AgentName = r2LiveM[1];
3978
4045
  var r2Toks = parseInt(r2LiveM[2], 10);
3979
4046
  var delAllEnts = document.querySelectorAll(\x27[data-r2-agent]\x27);
@@ -3981,35 +4048,31 @@ async function runStudio() {
3981
4048
  for (var rei = delAllEnts.length - 1; rei >= 0; rei--) {
3982
4049
  if (delAllEnts[rei].getAttribute(\x27data-r2-agent\x27) === r2AgentName) { r2Entry = delAllEnts[rei]; break; }
3983
4050
  }
3984
- if (!r2Entry) {
3985
- var delAllE = document.querySelectorAll(\x27.studio-log-entry\x27);
3986
- r2Entry = delAllE[delAllE.length - 1];
3987
- }
4051
+ if (!r2Entry) { var delAllE = document.querySelectorAll(\x27.studio-log-entry\x27); r2Entry = delAllE[delAllE.length - 1]; }
3988
4052
  if (r2Entry) {
3989
4053
  var r2Tb = r2Entry.querySelector(\x27.studio-log-entry__text\x27);
3990
4054
  if (r2Tb) r2Tb.innerHTML = \x27<span style="color:var(--green);font-family:var(--mono);font-size:10px">&#x2656; Deliberando Round 2 \u2014 \x27 + r2Toks + \x27 token<span class="thinking-dots"><span></span><span></span><span></span></span></span>\x27;
3991
4055
  }
3992
- studioAddTokens(0, 20); // approx chunk size
4056
+ studioAddTokens(0, 20);
3993
4057
  } else {
3994
- // Other status tokens — update last log entry
3995
4058
  var delEntries = document.querySelectorAll(\x27.studio-log-entry\x27);
3996
4059
  var delLast = delEntries[delEntries.length - 1];
3997
4060
  if (delLast) { var delTb = delLast.querySelector(\x27.studio-log-entry__text\x27); if (delTb) delTb.textContent = dev.token.replace(new RegExp(\x27[\\r\\n]+\x27,\x27g\x27),\x27 \x27); }
3998
4061
  }
3999
4062
  } else if (dev.deliberation_r2) {
4000
4063
  var r2d = dev.deliberation_r2;
4001
- // Full output in log — no truncation
4002
4064
  studioLog(r2d.label || r2d.agent, \x27&#x2656;\x27, \x27[R2] \x27 + (r2d.output || \x27\x27), \x27agent\x27, true);
4003
4065
  var ni2 = studioState.nodes.findIndex(function(x){return x.agent===r2d.agent;});
4004
- if (ni2 >= 0) {
4005
- studioState.nodes[ni2].output = r2d.output;
4006
- studioState.nodes[ni2].status = \x27done\x27;
4007
- }
4008
- // Estimate tokens for R2 (approx 1 token ≈ 4 chars)
4066
+ if (ni2 >= 0) { studioState.nodes[ni2].output = r2d.output; studioState.nodes[ni2].status = \x27done\x27; }
4009
4067
  studioAddTokens(0, Math.ceil((r2d.output||'').length / 4));
4068
+ // Mark this agent done in parliament block
4069
+ parlDoneAgents[r2d.label || r2d.agent] = true;
4070
+ parlActiveAgent = null;
4071
+ renderParlBlock(\x27r2\x27, null, null);
4010
4072
  renderStudioNodes();
4011
4073
  context = r2d.output || context;
4012
4074
  } else if (dev.deliberation_r3) {
4075
+ renderParlBlock(\x27r3\x27, null, null);
4013
4076
  studioLog(\x27HERALD\x27, \x27&#128295;\x27, \x27[Mediazione] \x27 + (dev.deliberation_r3.output || \x27\x27), \x27system\x27, true);
4014
4077
  studioAddTokens(0, Math.ceil((dev.deliberation_r3.output||'').length / 4));
4015
4078
  context = dev.deliberation_r3.output || context;
@@ -4017,6 +4080,7 @@ async function runStudio() {
4017
4080
  var r2Conv = Math.round((dev.r2_convergence || 0) * 100);
4018
4081
  studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Deliberazione completa — convergenza R2: \x27 + r2Conv + \x27%\x27, \x27system\x27);
4019
4082
  if (dev.mediation) { context = dev.mediation; }
4083
+ renderParlBlock(\x27done\x27, null, r2Conv);
4020
4084
  if (studioState.nodes[parlNodeIdx]) {
4021
4085
  studioState.nodes[parlNodeIdx].status = \x27done\x27;
4022
4086
  studioState.nodes[parlNodeIdx].label = \x27Parlamento (\x27 + r2Conv + \x27%)\x27;
@@ -4033,6 +4097,7 @@ async function runStudio() {
4033
4097
  } catch(e3) {
4034
4098
  if (e3.name !== \x27AbortError\x27) {
4035
4099
  studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Deliberazione non disponibile: \x27 + (e3.message || String(e3)), \x27error\x27);
4100
+ var pb2 = document.getElementById(\x27studioParliamentBlock\x27); if (pb2) pb2.style.display = \x27none\x27;
4036
4101
  }
4037
4102
  }
4038
4103
  }
@@ -4068,6 +4133,7 @@ function saveStudioSession(task, nodes, log, result) {
4068
4133
  task: task,
4069
4134
  nodes: nodes.map(function(n){return {label:n.label,icon:n.icon,agent:n.agent};}),
4070
4135
  result: result,
4136
+ canvas: studioState.canvas || null,
4071
4137
  log: log.map(function(e){return {agent:e.agent,icon:e.icon,text:e.text,type:e.type,time:e.time};}),
4072
4138
  ts: new Date().toLocaleString()
4073
4139
  });
@@ -4111,10 +4177,28 @@ function restoreStudioSession(idx) {
4111
4177
  studioState.nodes = s.nodes.map(function(n){return {icon:n.icon,agent:n.agent,label:n.label,status:'done'};});
4112
4178
  studioState.log = s.log;
4113
4179
  studioState.result = s.result;
4180
+ studioState.canvas = s.canvas || null;
4114
4181
  studioState.running = false;
4115
4182
  var ta = document.getElementById('studioTaskInput');
4116
4183
  if (ta) ta.value = s.task;
4117
4184
  renderStudioNodes(); renderStudioLog(); renderStudioResult();
4185
+ // Restore canvas if present
4186
+ if (s.canvas) {
4187
+ var cf = document.getElementById('canvasFrame');
4188
+ var cp = document.getElementById('canvasPanel');
4189
+ if (cf) cf.srcdoc = s.canvas;
4190
+ if (cp) cp.classList.add('open');
4191
+ var ct = document.getElementById('canvasTitle');
4192
+ if (ct) ct.textContent = 'Studio Report';
4193
+ var scb = document.getElementById('studioCanvasBtn');
4194
+ if (scb) scb.style.display = '';
4195
+ // Also store in allCanvasData so canvas panel nav works
4196
+ var convId = activeConvId || 'studio';
4197
+ if (!allCanvasData[convId]) allCanvasData[convId] = {canvases:[], browsers:[]};
4198
+ allCanvasData[convId].canvases.push({html: s.canvas, title: s.task.slice(0,60), ts: s.ts});
4199
+ canvasIdx = allCanvasData[convId].canvases.length - 1;
4200
+ canvasMode = 'canvas';
4201
+ }
4118
4202
  showToast('success', 'Session restored', s.task.slice(0, 60), 3000);
4119
4203
  }
4120
4204
 
@@ -4417,6 +4501,7 @@ function renderStudio(el) {
4417
4501
  '<button id="studioCanvasBtn" onclick="openCanvasPanel()" title="' + t('canvas_open') + '" style="font-size:12px;padding:5px 14px;background:none;border:1px solid var(--border);border-radius:6px;color:var(--dim);cursor:pointer;font-weight:700;transition:all .2s">&#9632; Canvas</button>' +
4418
4502
  '</div>' +
4419
4503
  '<div class="studio-canvas" id="studioNodes"></div>' +
4504
+ '<div id="studioParliamentBlock" style="display:none"></div>' +
4420
4505
  '<div class="studio-log" id="studioLog" style="display:none"></div>' +
4421
4506
  '<div id="studioResult"></div>' +
4422
4507
  '<div id="studioSessionsBar" style="margin-top:16px;display:none"></div>' +
@@ -4962,6 +5047,24 @@ input:focus,textarea:focus{border-color:var(--green3)}
4962
5047
  .studio-arrow{display:flex;align-items:center;color:var(--border2);font-size:18px;padding:0 8px;flex-shrink:0;margin-bottom:30px;transition:color .4s}
4963
5048
  .studio-arrow--active{color:var(--green3);animation:stFlow .5s ease-in-out infinite alternate}
4964
5049
  .studio-arrow--done{color:#22c55e}
5050
+ .studio-parliament{background:linear-gradient(135deg,#0e0e1c 0%,#12122a 100%);border:1.5px solid #6366f1;border-radius:12px;padding:18px 20px;margin-bottom:16px;animation:stNodeIn .4s ease forwards}
5051
+ .studio-parliament__header{display:flex;align-items:center;gap:10px;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid #6366f133}
5052
+ .studio-parliament__master{display:flex;align-items:center;gap:8px;background:linear-gradient(90deg,#4f46e5,#7c3aed);border-radius:8px;padding:7px 12px;font-size:11px;font-weight:700;color:#fff;letter-spacing:.5px}
5053
+ .studio-parliament__master-icon{font-size:18px}
5054
+ .studio-parliament__title{font-size:10px;color:#a5b4fc;text-transform:uppercase;letter-spacing:1.2px;font-weight:700;margin-left:auto}
5055
+ .studio-parliament__round{font-size:10px;color:#6366f1;background:rgba(99,102,241,.12);border-radius:20px;padding:3px 10px;font-weight:700;animation:stPulse .9s ease-in-out infinite}
5056
+ .studio-parliament__agents{display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap;padding:4px 0}
5057
+ .studio-parliament__agent{display:flex;flex-direction:column;align-items:center;gap:5px;min-width:80px}
5058
+ .studio-parliament__agent-circle{width:44px;height:44px;border-radius:10px;border:1.5px solid #6366f1;background:#1a1a2e;display:flex;align-items:center;justify-content:center;font-size:18px;transition:all .3s}
5059
+ .studio-parliament__agent-circle--reading{border-color:#22d3ee;box-shadow:0 0 0 6px rgba(34,211,238,.15),0 0 16px rgba(34,211,238,.2);animation:stRing 1.4s ease-out infinite}
5060
+ .studio-parliament__agent-circle--done{border-color:#22c55e;background:rgba(34,197,94,.08)}
5061
+ .studio-parliament__agent-label{font-size:9px;color:#a5b4fc;text-align:center;font-weight:600;max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
5062
+ .studio-parliament__conn{display:flex;flex-direction:column;align-items:center;gap:2px;margin-bottom:16px}
5063
+ .studio-parliament__conn-line{width:2px;height:18px;background:linear-gradient(180deg,#6366f1,#22d3ee);border-radius:2px;animation:stFlow .7s ease-in-out infinite alternate}
5064
+ .studio-parliament__bidir{color:#6366f1;font-size:14px;animation:stPulse .6s ease-in-out infinite}
5065
+ .studio-parliament__reading{font-size:9px;color:#22d3ee;text-align:center;margin-top:6px;font-family:var(--mono);background:rgba(34,211,238,.07);border-radius:6px;padding:3px 8px}
5066
+ .studio-parliament__convergence{text-align:center;margin-top:12px;padding:8px;background:rgba(34,197,94,.07);border:1px solid rgba(34,197,94,.2);border-radius:8px;font-size:11px;color:#22c55e;font-weight:700}
5067
+ @keyframes stOrbit{0%{transform:translateX(0) scale(1)}50%{transform:translateX(4px) scale(1.04)}100%{transform:translateX(0) scale(1)}}
4965
5068
  .studio-log{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:16px;max-height:380px;overflow-y:auto;font-size:11.5px;line-height:1.65}
4966
5069
  .studio-log-entry{margin-bottom:12px;padding:10px 12px;border-radius:8px;background:var(--bg3);border:1px solid var(--border)}
4967
5070
  .studio-log-entry:last-child{margin-bottom:0}