nothumanallowed 13.4.6 → 13.4.7

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.4.6",
3
+ "version": "13.4.7",
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.4.6';
8
+ export const VERSION = '13.4.7';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -3408,6 +3408,137 @@ function studioLog(agent, icon, text, type, update) {
3408
3408
  renderStudioLog();
3409
3409
  }
3410
3410
 
3411
+ // ── Workflow node character SVG ──────────────────────────────────────────────
3412
+ // Generates a compact animated "agent at desk" illustration for each pipeline node card.
3413
+ // isRunning = prl-arm/prl-head animations active; isDone = green checkmark.
3414
+ function buildWorkflowChar(n) {
3415
+ var lbl = n.label || n.agent || \x27?\x27;
3416
+ var ico = n.icon || \x27■\x27;
3417
+ var isActive = n.status === \x27running\x27;
3418
+ var isDone = n.status === \x27done\x27;
3419
+ var skinColors = [\x27#fbbf24\x27,\x27#f97316\x27,\x27#a78bfa\x27,\x27#34d399\x27,\x27#60a5fa\x27,\x27#f472b6\x27];
3420
+ var skinIdx = Math.abs((lbl.charCodeAt(0)||65)+(lbl.charCodeAt(lbl.length-1)||90)) % skinColors.length;
3421
+ var skin = skinColors[skinIdx];
3422
+ var shirtColors = [\x27#4f46e5\x27,\x27#0891b2\x27,\x27#7c3aed\x27,\x27#059669\x27,\x27#dc2626\x27,\x27#d97706\x27];
3423
+ var shirt = shirtColors[skinIdx];
3424
+ var hairColors = [\x27#1a1a1a\x27,\x27#4a3728\x27,\x27#c4a35a\x27,\x27#8b0000\x27,\x27#2c4a7c\x27,\x27#3d2b1f\x27];
3425
+ var hair = hairColors[skinIdx];
3426
+ var accentColor = isActive ? \x27#6366f1\x27 : (isDone ? \x27#22c55e\x27 : \x27#333360\x27);
3427
+ var deskBg = isDone ? \x27#1a3a1a\x27 : (isActive ? \x27#1a1a3e\x27 : \x27#1a1a2a\x27);
3428
+ var monGlow = isActive ? \x27filter:drop-shadow(0 0 4px #6366f1)\x27 : \x27\x27;
3429
+ var armCls = isActive ? \x27class="prl-arm"\x27 : \x27\x27;
3430
+ var headCls = isActive ? \x27class="prl-head"\x27 : \x27\x27;
3431
+ var glowStyle = isActive ? \x27filter:drop-shadow(0 0 5px #6366f1)\x27 : \x27\x27;
3432
+ var svg = \x27<svg viewBox="0 0 80 96" width="70" height="84" xmlns="http://www.w3.org/2000/svg" style="\x27+glowStyle+\x27;display:block;margin:0 auto">\x27+
3433
+ // Desk
3434
+ \x27<path d="M4 55 L76 55 L76 63 L4 63 Z" fill="\x27+deskBg+\x27" stroke="\x27+accentColor+\x27" stroke-width="1.2"/>\x27+
3435
+ \x27<path d="M4 63 L76 63 L76 70 L4 70 Z" fill="#0e0e1c"/>\x27+
3436
+ \x27<line x1="4" y1="63" x2="76" y2="63" stroke="\x27+accentColor+\x2760" stroke-width=".8"/>\x27+
3437
+ \x27<path d="M10 70 C10 70 9 82 9 84 C9 86 11 87 13 87 C15 87 17 86 17 84 C17 82 16 70 16 70 Z" fill="#111128"/>\x27+
3438
+ \x27<path d="M63 70 C63 70 62 82 62 84 C62 86 64 87 66 87 C68 87 70 86 70 84 C70 82 69 70 69 70 Z" fill="#111128"/>\x27+
3439
+ \x27<rect x="17" y="79" width="46" height="3" rx="1.5" fill="#161626"/>\x27+
3440
+ // Monitor
3441
+ \x27<ellipse cx="35" cy="56" rx="14" ry="2" fill="rgba(0,0,0,.4)"/>\x27+
3442
+ \x27<ellipse cx="35" cy="57" rx="7" ry="1.5" fill="#1a1a2e"/>\x27+
3443
+ \x27<rect x="33" y="50" width="4" height="6" rx="1" fill="#1a1a2e"/>\x27+
3444
+ \x27<rect x="17" y="26" width="36" height="25" rx="4" fill="#050510"/>\x27+
3445
+ \x27<rect x="18" y="27" width="34" height="23" rx="3" fill="#0d0d20" stroke="\x27+accentColor+\x27" stroke-width="\x27+(isActive?\x272\x27:\x271\x27)+\x27" style="\x27+monGlow+\x27"/>\x27+
3446
+ \x27<rect x="20" y="29" width="30" height="18" rx="2" fill="#0a0a18"/>\x27+
3447
+ (isActive ?
3448
+ \x27<defs><linearGradient id="wsg\x27+skinIdx+\x27" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#6366f122"/><stop offset="1" stop-color="#6366f108"/></linearGradient></defs>\x27+
3449
+ \x27<rect x="20" y="29" width="30" height="18" rx="2" fill="url(#wsg\x27+skinIdx+\x27)"/>\x27+
3450
+ \x27<line x1="22" y1="32" x2="48" y2="32" stroke="#6366f1ee" stroke-width="1.2" stroke-linecap="round"/>\x27+
3451
+ \x27<line x1="22" y1="35" x2="44" y2="35" stroke="#6366f1aa" stroke-width="1" stroke-linecap="round"/>\x27+
3452
+ \x27<line x1="22" y1="38" x2="46" y2="38" stroke="#6366f188" stroke-width="1" stroke-linecap="round"/>\x27+
3453
+ \x27<line x1="22" y1="41" x2="40" y2="41" stroke="#6366f166" stroke-width="1" stroke-linecap="round"/>\x27+
3454
+ \x27<line x1="22" y1="44" x2="43" y2="44" stroke="#6366f144" stroke-width="1" stroke-linecap="round"/>\x27
3455
+ :
3456
+ \x27<line x1="22" y1="33" x2="46" y2="33" stroke="#1e1e30" stroke-width="1" stroke-linecap="round"/>\x27+
3457
+ \x27<line x1="22" y1="36" x2="42" y2="36" stroke="#1e1e30" stroke-width="1" stroke-linecap="round"/>\x27+
3458
+ \x27<line x1="22" y1="39" x2="44" y2="39" stroke="#1e1e30" stroke-width="1" stroke-linecap="round"/>\x27+
3459
+ \x27<line x1="22" y1="42" x2="38" y2="42" stroke="#1e1e30" stroke-width="1" stroke-linecap="round"/>\x27
3460
+ )+
3461
+ \x27<circle cx="35" cy="28.2" r=".9" fill="\x27+(isActive?\x27#6366f1\x27:\x27#2a2a40\x27)+\x27"/>\x27+
3462
+ // Keyboard
3463
+ \x27<rect x="13" y="48" width="36" height="7" rx="2.5" fill="#0c0c1e" stroke="#202036" stroke-width="1"/>\x27+
3464
+ \x27<rect x="14" y="49.5" width="3" height="2" rx=".5" fill="#181830"/>\x27+
3465
+ \x27<rect x="18" y="49.5" width="3" height="2" rx=".5" fill="#181830"/>\x27+
3466
+ \x27<rect x="22" y="49.5" width="3" height="2" rx=".5" fill="#181830"/>\x27+
3467
+ \x27<rect x="26" y="49.5" width="3" height="2" rx=".5" fill="#181830"/>\x27+
3468
+ \x27<rect x="30" y="49.5" width="3" height="2" rx=".5" fill="#181830"/>\x27+
3469
+ \x27<rect x="34" y="49.5" width="3" height="2" rx=".5" fill="#181830"/>\x27+
3470
+ \x27<rect x="38" y="49.5" width="3" height="2" rx=".5" fill="#181830"/>\x27+
3471
+ \x27<rect x="15" y="52.5" width="5" height="2" rx=".5" fill="#181830"/>\x27+
3472
+ \x27<rect x="21" y="52.5" width="5" height="2" rx=".5" fill="#181830"/>\x27+
3473
+ \x27<rect x="27" y="52.5" width="5" height="2" rx=".5" fill="#181830"/>\x27+
3474
+ \x27<rect x="33" y="52.5" width="5" height="2" rx=".5" fill="#181830"/>\x27+
3475
+ \x27<rect x="19" y="55" width="24" height="1.8" rx=".9" fill="#181830"/>\x27+
3476
+ // Chair
3477
+ \x27<ellipse cx="34" cy="72" rx="12" ry="4" fill="#111124"/>\x27+
3478
+ \x27<rect x="32" y="65" width="4" height="8" rx="1" fill="#1a1a2c"/>\x27+
3479
+ \x27<path d="M22 60 Q22 56 34 56 Q46 56 46 60 L46 66 Q46 68 34 68 Q22 68 22 66 Z" fill="#1c1c2c" stroke="#2a2a3e" stroke-width="1"/>\x27+
3480
+ \x27<path d="M24 44 Q23 38 34 37 Q45 38 44 44 L44 58 Q44 60 34 60 Q24 60 24 58 Z" fill="#191928" stroke="#2a2a3c" stroke-width="1"/>\x27+
3481
+ \x27<path d="M26 46 Q26 41 34 40 Q42 41 42 46 L42 57 Q42 58 34 58 Q26 58 26 57 Z" fill="#1e1e30"/>\x27+
3482
+ // Shirt
3483
+ \x27<path d="M27 44 Q27 42 34 41 Q41 42 41 44 L42 58 L26 58 Z" fill="\x27+shirt+\x27"/>\x27+
3484
+ \x27<path d="M27 44 Q27 42 34 41 L34 58 L26 58 Z" fill="rgba(0,0,0,.12)"/>\x27+
3485
+ \x27<path d="M34 41 L31 46 L34 44.5 L37 46 Z" fill="\x27+skin+\x27ee"/>\x27+
3486
+ // Arms
3487
+ \x27<g \x27+armCls+\x27>\x27+
3488
+ \x27<path d="M28 45 C24 47 22 50 21 53 C21 55 23 56 25 55 C27 54 27 52 28 49 Z" fill="\x27+shirt+\x27"/>\x27+
3489
+ \x27<path d="M21 53 C19 55 18 57 18 59 C18 61 20 62 22 61 C24 60 24 58 25 55 Z" fill="\x27+skin+\x27"/>\x27+
3490
+ \x27<ellipse cx="19" cy="60" rx="4" ry="3" fill="\x27+skin+\x27" transform="rotate(-10 19 60)"/>\x27+
3491
+ \x27<path d="M40 45 C44 47 46 50 47 53 C47 55 45 56 43 55 C41 54 41 52 40 49 Z" fill="\x27+shirt+\x27"/>\x27+
3492
+ \x27<path d="M47 53 C49 55 50 57 50 59 C50 61 48 62 46 61 C44 60 44 58 43 55 Z" fill="\x27+skin+\x27"/>\x27+
3493
+ \x27<ellipse cx="49" cy="60" rx="4" ry="3" fill="\x27+skin+\x27" transform="rotate(10 49 60)"/>\x27+
3494
+ \x27</g>\x27+
3495
+ // Head
3496
+ \x27<g \x27+headCls+\x27>\x27+
3497
+ \x27<path d="M30 40 L38 40 L38 43 Q38 45 34 45 Q30 45 30 43 Z" fill="\x27+skin+\x27"/>\x27+
3498
+ \x27<ellipse cx="34" cy="29" rx="11" ry="12.5" fill="\x27+skin+\x27"/>\x27+
3499
+ \x27<path d="M23 28 C21 28 20 30 20 31.5 C20 33 21 34.5 23 34.5 C24 34.5 24.5 33.5 24 31.5 C24.5 29.5 24 28 23 28" fill="\x27+skin+\x27"/>\x27+
3500
+ \x27<path d="M45 28 C47 28 48 30 48 31.5 C48 33 47 34.5 45 34.5 C44 34.5 43.5 33.5 44 31.5 C43.5 29.5 44 28 45 28" fill="\x27+skin+\x27"/>\x27+
3501
+ \x27<path d="M23 28 C22 22 24 16 34 15 C44 16 46 22 45 28 C44 22 42 18 34 17 C26 18 24 22 23 28" fill="\x27+hair+\x27"/>\x27+
3502
+ \x27<path d="M28 17 C30 15 33 15 36 16 C33 14 29 15 28 17" fill="rgba(255,255,255,.12)"/>\x27+
3503
+ \x27<path d="M27 23 Q29.5 21.5 32 23" stroke="\x27+hair+\x27" stroke-width="1.6" fill="none" stroke-linecap="round"/>\x27+
3504
+ \x27<path d="M36 23 Q38.5 21.5 41 23" stroke="\x27+hair+\x27" stroke-width="1.6" fill="none" stroke-linecap="round"/>\x27+
3505
+ \x27<ellipse cx="30" cy="27.5" rx="3" ry="3.5" fill="#fff"/>\x27+
3506
+ \x27<ellipse cx="38" cy="27.5" rx="3" ry="3.5" fill="#fff"/>\x27+
3507
+ \x27<circle cx="30" cy="28" r="2.2" fill="#3d4a6b"/>\x27+
3508
+ \x27<circle cx="38" cy="28" r="2.2" fill="#3d4a6b"/>\x27+
3509
+ \x27<circle cx="30" cy="28" r="1.3" fill="#0a0a14"/>\x27+
3510
+ \x27<circle cx="38" cy="28" r="1.3" fill="#0a0a14"/>\x27+
3511
+ \x27<circle cx="31" cy="27" r=".8" fill="rgba(255,255,255,.9)"/>\x27+
3512
+ \x27<circle cx="39" cy="27" r=".8" fill="rgba(255,255,255,.9)"/>\x27+
3513
+ \x27<path d="M33 31 Q33 33 34 33.5 Q35 34 35 33 Q36 33 36 31" stroke="\x27+skin+\x27" stroke-width="1.1" fill="none" opacity=".7"/>\x27+
3514
+ (isDone ?
3515
+ \x27<path d="M29 37 Q34 42 39 37" stroke="#8b4513" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
3516
+ \x27<path d="M30 37.5 Q34 41 38 37.5 Q34 40 30 37.5" fill="#fff" opacity=".8"/>\x27
3517
+ :
3518
+ \x27<path d="M30.5 37 Q34 38.5 37.5 37" stroke="#8b4513" stroke-width="1.4" fill="none" stroke-linecap="round"/>\x27
3519
+ )+
3520
+ \x27<circle cx="38.5" cy="48" r="5.5" fill="#0f0f1e" stroke="\x27+shirt+\x2780" stroke-width="1.2"/>\x27+
3521
+ \x27<circle cx="38.5" cy="48" r="4" fill="#161622"/>\x27+
3522
+ \x27<text x="38.5" y="51" text-anchor="middle" font-size="6">\x27+ico+\x27</text>\x27+
3523
+ \x27</g>\x27+
3524
+ (isDone ?
3525
+ \x27<circle cx="67" cy="9" r="9" fill="#0a2010"/>\x27+
3526
+ \x27<circle cx="67" cy="9" r="7" fill="#16a34a"/>\x27+
3527
+ \x27<circle cx="67" cy="9" r="5.5" fill="#22c55e"/>\x27+
3528
+ \x27<path d="M63 9 L66 12 L71 5" stroke="#fff" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>\x27
3529
+ : \x27\x27)+
3530
+ // Flying papers when running
3531
+ (isActive ?
3532
+ \x27<g class="prl-fly-doc" style="animation-delay:.2s;animation-duration:1.6s;transform-origin:35px 50px">\x27+
3533
+ \x27<path d="M0,0 L11,0 L14,3 L14,18 L0,18 Z" fill="#0d0d20" stroke="#6366f1" stroke-width="1.2" transform="translate(35,50)"/>\x27+
3534
+ \x27<line x1="37" y1="53" x2="47" y2="53" stroke="#6366f1" stroke-width=".8" opacity=".7"/>\x27+
3535
+ \x27<line x1="37" y1="56" x2="47" y2="56" stroke="#6366f1" stroke-width=".8" opacity=".5"/>\x27+
3536
+ \x27</g>\x27
3537
+ : \x27\x27)+
3538
+ \x27</svg>\x27;
3539
+ return svg;
3540
+ }
3541
+
3411
3542
  function renderStudioNodes() {
3412
3543
  var el = document.getElementById('studioNodes');
3413
3544
  if (!el) return;
@@ -3417,29 +3548,41 @@ function renderStudioNodes() {
3417
3548
  }
3418
3549
  var html = '<div class="studio-nodes">';
3419
3550
  studioState.nodes.forEach(function(n, i) {
3551
+ var isActive = n.status === 'running';
3552
+ var isDone = n.status === 'done';
3553
+ var isErr = n.status === 'error';
3554
+ var isWait = !isActive && !isDone && !isErr;
3420
3555
  var cls = 'studio-node';
3421
- if (n.status === 'running') cls += ' studio-node--active';
3422
- else if (n.status === 'done') cls += ' studio-node--done';
3423
- else if (n.status === 'error') cls += ' studio-node--error';
3424
- var statusLabel = {waiting:'&#9711; wait', running:'&#9654; running', done:'&#10003; done', error:'&#10005; error'}[n.status] || '';
3425
- // Only animate nodes that haven't been rendered yet (first appearance)
3556
+ if (isActive) cls += ' studio-node--active';
3557
+ else if (isDone) cls += ' studio-node--done';
3558
+ else if (isErr) cls += ' studio-node--error';
3559
+ // Only animate entrance on first appearance
3426
3560
  var style = n._rendered ? '' : 'animation-delay:' + (i * 110) + 'ms';
3427
- html += '<div class="' + cls + '" data-agent-label="' + esc(n.label || n.agent) + '" style="' + style + ';cursor:pointer" onclick="studioScrollToAgent(this.getAttribute(String.fromCharCode(100,97,116,97,45,97,103,101,110,116,45,108,97,98,101,108)))" title="Vai al log di ' + esc(n.label || n.agent) + '">';
3428
- html += '<div class="studio-node__circle">' + n.icon + '</div>';
3429
- html += '<div class="studio-node__label">' + esc(n.label) + '</div>';
3561
+ html += '<div class="' + cls + '" data-agent-label="' + esc(n.label || n.agent) + '" style="' + style + ';cursor:pointer" onclick="studioScrollToAgent(this.getAttribute(String.fromCharCode(100,97,116,97,45,97,103,101,110,116,45,108,97,98,101,108)))" title="' + esc(n.label || n.agent) + '">';
3562
+ if (isActive || isDone) {
3563
+ // Show animated office character
3564
+ html += '<div class="studio-node__char">' + buildWorkflowChar(n) + '</div>';
3565
+ if (isActive) {
3566
+ var desc = n.label || n.agent;
3567
+ html += '<div class="studio-node__bubble prl-action-bubble prl-action-bubble--active">...analizza</div>';
3568
+ } else {
3569
+ html += '<div class="studio-node__bubble prl-action-bubble" style="background:#0a2010;border-color:#22c55e;color:#4ade80">\u2714 completato</div>';
3570
+ }
3571
+ html += '<div class="studio-node__label studio-node__label--char">' + esc(n.label) + '</div>';
3572
+ } else {
3573
+ // Waiting / error: keep original compact pill
3574
+ html += '<div class="studio-node__circle">' + n.icon + '</div>';
3575
+ html += '<div class="studio-node__label">' + esc(n.label) + '</div>';
3576
+ }
3430
3577
  if (n.reason) {
3431
3578
  html += '<div class="studio-node__reason" onclick="event.stopPropagation();this.classList.toggle(String.fromCharCode(111,112,101,110))" title="' + esc(n.reason) + '">&#x2139;<span class="studio-node__reason-tip">' + esc(n.reason) + '</span></div>';
3432
3579
  }
3433
- html += '<div class="studio-node__status studio-node__status--' + n.status + '">' + statusLabel + '</div>';
3434
- if (n.status === 'running') {
3435
- html += '<div class="studio-node__progress"><span></span><span></span><span></span></div>';
3436
- }
3437
3580
  html += '</div>';
3438
3581
  if (i < studioState.nodes.length - 1) {
3439
3582
  var next = studioState.nodes[i + 1];
3440
3583
  var arrowCls = 'studio-arrow';
3441
- if (n.status === 'done' && next.status === 'running') arrowCls += ' studio-arrow--active';
3442
- else if (n.status === 'done') arrowCls += ' studio-arrow--done';
3584
+ if (isDone && next.status === 'running') arrowCls += ' studio-arrow--active';
3585
+ else if (isDone) arrowCls += ' studio-arrow--done';
3443
3586
  var arrowStyle = n._rendered ? '' : 'opacity:0;animation:stNodeIn .3s ease ' + (i * 110 + 55) + 'ms forwards';
3444
3587
  html += '<div class="' + arrowCls + '" style="' + arrowStyle + '">&#8594;</div>';
3445
3588
  }
@@ -4099,12 +4242,8 @@ async function runStudio() {
4099
4242
  studioState.nodes[i].tokensIn = stepResult.tokensIn || 0;
4100
4243
  studioState.nodes[i].tokensOut = stepResult.tokensOut || 0;
4101
4244
  studioLog(node.label, node.icon, realOutput || (stepResult.canvas ? '[Canvas report generated]' : '(done)'), 'agent', true);
4102
- // If CanvasAgent produced HTML, pre-load the frame but do NOT auto-open the panel.
4103
- // Auto-opening would cover the parliament animation block if parliament mode is active.
4104
- if (stepResult.canvas) {
4105
- var cf = document.getElementById('canvasFrame');
4106
- if (cf) cf.srcdoc = stepResult.canvas;
4107
- }
4245
+ // allCanvasData already updated inside runStudioStep streaming handler.
4246
+ // Keep _canvasFrameLoadedHtml in sync so openCanvasPanel knows what\x27s loaded.
4108
4247
  // Accumulate context: append each step's output so specialist agents see ALL previous data
4109
4248
  if (realOutput) {
4110
4249
  context = context
@@ -5003,12 +5142,23 @@ function runStudioStep(idx, node, task, context, stepDef, signal) {
5003
5142
  if (ev.canvas) {
5004
5143
  canvasHtml = ev.canvas;
5005
5144
  studioState.canvas = ev.canvas;
5006
- // Pre-load the canvas HTML into the frame but do NOT auto-open the panel —
5007
- // opening it mid-run would hide the parliament animation block from view.
5008
- // The user opens the canvas manually via the Canvas button.
5009
- var cf2 = document.getElementById('canvasFrame');
5145
+ // Store in allCanvasData so openCanvasPanel() finds it reliably
5146
+ // (studioState.canvas can be stale if session is restored from localStorage)
5147
+ var _cid = activeConvId || \x27_default\x27;
5148
+ if (!allCanvasData[_cid]) allCanvasData[_cid] = {canvases:[], browsers:[]};
5149
+ // Replace last studio canvas if it exists, otherwise push
5150
+ var _cd = allCanvasData[_cid];
5151
+ var _existIdx = _cd.canvases.findIndex(function(c){ return c.title === \x27Studio Report\x27; });
5152
+ var _citem = {html: ev.canvas, title: \x27Studio Report\x27, ts: new Date().toLocaleTimeString()};
5153
+ if (_existIdx >= 0) { _cd.canvases[_existIdx] = _citem; canvasIdx = _existIdx; }
5154
+ else { _cd.canvases.push(_citem); canvasIdx = _cd.canvases.length - 1; }
5155
+ canvasMode = \x27canvas\x27;
5156
+ // Pre-load the canvas HTML into the frame tracker — do NOT open the panel
5157
+ // (opening mid-run would hide the parliament animation block).
5158
+ _canvasFrameLoadedHtml = ev.canvas;
5159
+ var cf2 = document.getElementById(\x27canvasFrame\x27);
5010
5160
  if (cf2) cf2.srcdoc = canvasHtml;
5011
- var scb = document.getElementById('studioCanvasBtn');
5161
+ var scb = document.getElementById(\x27studioCanvasBtn\x27);
5012
5162
  if (scb) {
5013
5163
  scb.style.display = \x27\x27;
5014
5164
  scb.style.background = \x27var(--greendim)\x27;
@@ -5723,6 +5873,13 @@ input:focus,textarea:focus{border-color:var(--green3)}
5723
5873
  .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}
5724
5874
  .studio-arrow--active{color:var(--green3);animation:stFlow .5s ease-in-out infinite alternate}
5725
5875
  .studio-arrow--done{color:#22c55e}
5876
+ /* ── Workflow node animated character ── */
5877
+ .studio-nodes{align-items:flex-end!important;min-height:190px!important}
5878
+ .studio-node--active,.studio-node--done{min-width:118px!important;max-width:140px!important;gap:4px!important}
5879
+ .studio-node__char{display:flex;align-items:center;justify-content:center;width:100%}
5880
+ .studio-node__bubble{font-size:9px;padding:2px 7px;border-radius:20px;white-space:nowrap;text-align:center;margin:0 auto}
5881
+ .studio-node__label--char{font-size:10px;text-align:center;max-width:130px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:700;letter-spacing:.3px;color:#6366f1}
5882
+ .studio-node--done .studio-node__label--char{color:#22c55e}
5726
5883
  /* ── Parliament Office Cartoon ── */
5727
5884
  .prl-wrap{background:#07070f;border:1.5px solid #6366f1;border-radius:14px;padding:14px 16px 12px;margin-bottom:16px;animation:stNodeIn .35s ease forwards;overflow:hidden}
5728
5885
  #studioParliamentBlock[style*="sticky"] .prl-wrap{animation:stNodeIn .35s ease forwards,parlPulse 2.2s ease-in-out infinite}