antri_cli 1.57.0 → 1.57.2

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.
@@ -264,12 +264,348 @@ export class DesktopServer {
264
264
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
265
265
  res.end(content);
266
266
  }
267
+ else if (artifact.type === 'mindmap') {
268
+ const htmlMindmap = `<!DOCTYPE html>
269
+ <html lang="en">
270
+ <head>
271
+ <meta charset="UTF-8">
272
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
273
+ <title>${artifact.title}</title>
274
+ <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
275
+ <script src="https://cdn.jsdelivr.net/npm/markmap-view@0.17.2"></script>
276
+ <script src="https://cdn.jsdelivr.net/npm/markmap-lib@0.17.2"></script>
277
+ <style>
278
+ * { box-sizing: border-box; margin: 0; padding: 0; }
279
+ :root {
280
+ --bg-page: radial-gradient(circle at 15% 15%, rgba(99, 102, 241, 0.08) 0%, transparent 40%), radial-gradient(circle at 85% 85%, rgba(236, 72, 153, 0.08) 0%, transparent 40%), #f8fafc;
281
+ --bg-viewport: #ffffff;
282
+ --border-viewport: rgba(226, 232, 240, 0.9);
283
+ --text-main: #0f172a;
284
+ --text-muted: #64748b;
285
+ --btn-bg: #ffffff;
286
+ --btn-border: #e2e8f0;
287
+ --btn-text: #1e293b;
288
+ --btn-hover: #f1f5f9;
289
+ --shadow-viewport: 0 20px 45px -15px rgba(0, 0, 0, 0.06), 0 0 1px 1px rgba(0, 0, 0, 0.04);
290
+ --node-bg: #ffffff;
291
+ --node-border: #cbd5e1;
292
+ --node-text: #0f172a;
293
+ --root-bg: #4f46e5;
294
+ --root-border: #4338ca;
295
+ --root-text: #ffffff;
296
+ --link-stroke: #818cf8;
297
+ --badge-bg: rgba(168, 85, 247, 0.12);
298
+ --badge-color: #7e22ce;
299
+ --badge-border: rgba(168, 85, 247, 0.25);
300
+ }
301
+ body.theme-dark {
302
+ --bg-page: radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.25) 0%, transparent 50%), radial-gradient(circle at 90% 80%, rgba(236, 72, 153, 0.22) 0%, transparent 50%), radial-gradient(circle at 50% 50%, rgba(6, 182, 212, 0.18) 0%, transparent 60%), #0d1322;
303
+ --bg-viewport: rgba(18, 24, 38, 0.9);
304
+ --border-viewport: rgba(255, 255, 255, 0.1);
305
+ --text-main: #f8fafc;
306
+ --text-muted: #94a3b8;
307
+ --btn-bg: rgba(30, 41, 59, 0.85);
308
+ --btn-border: rgba(255, 255, 255, 0.12);
309
+ --btn-text: #f8fafc;
310
+ --btn-hover: rgba(51, 65, 85, 0.95);
311
+ --shadow-viewport: 0 20px 45px -15px rgba(0, 0, 0, 0.5);
312
+ --node-bg: #1e293b;
313
+ --node-border: #334155;
314
+ --node-text: #f8fafc;
315
+ --root-bg: #6366f1;
316
+ --root-border: #818cf8;
317
+ --root-text: #ffffff;
318
+ --link-stroke: #6366f1;
319
+ --badge-bg: rgba(168, 85, 247, 0.2);
320
+ --badge-color: #c084fc;
321
+ --badge-border: rgba(168, 85, 247, 0.35);
322
+ }
323
+ body {
324
+ margin: 0; padding: 20px;
325
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
326
+ background: var(--bg-page);
327
+ color: var(--text-main);
328
+ display: flex; flex-direction: column; align-items: center; min-height: 100vh;
329
+ overflow-x: hidden;
330
+ transition: background 0.3s ease;
331
+ }
332
+ .header { text-align: center; margin-bottom: 14px; }
333
+ h1 { font-size: 19px; color: var(--text-main); margin: 0 0 6px 0; font-weight: 800; letter-spacing: -0.3px; }
334
+ .badge {
335
+ font-size: 11px;
336
+ background: var(--badge-bg);
337
+ color: var(--badge-color);
338
+ padding: 4px 12px; border-radius: 9999px;
339
+ border: 1px solid var(--badge-border);
340
+ text-transform: uppercase; font-weight: 700; letter-spacing: 0.5px;
341
+ display: inline-block; margin-bottom: 6px;
342
+ }
343
+ .toolbar {
344
+ display: flex; gap: 8px; margin-bottom: 14px; align-items: center; flex-wrap: wrap; justify-content: center;
345
+ }
346
+ .tool-btn {
347
+ background: var(--btn-bg); border: 1px solid var(--btn-border);
348
+ color: var(--btn-text); padding: 7px 14px; border-radius: 9px; font-size: 12px;
349
+ font-weight: 700; cursor: pointer; transition: all 0.15s; display: flex; align-items: center; gap: 6px;
350
+ box-shadow: 0 2px 4px rgba(0,0,0,0.03);
351
+ }
352
+ .tool-btn:hover { background: var(--btn-hover); transform: translateY(-1px); }
353
+ .tool-btn:active { transform: translateY(0); }
354
+ .viewport {
355
+ width: 100%; max-width: 1200px; height: 75vh; min-height: 540px;
356
+ position: relative; overflow: hidden; border-radius: 18px;
357
+ background: var(--bg-viewport); backdrop-filter: blur(20px);
358
+ border: 1px solid var(--border-viewport);
359
+ box-shadow: var(--shadow-viewport);
360
+ touch-action: none;
361
+ }
362
+ #mindmapSvg {
363
+ width: 100%; height: 100%;
364
+ display: block;
365
+ cursor: grab;
366
+ }
367
+ #mindmapSvg:active { cursor: grabbing; }
368
+ .markmap-foreign {
369
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
370
+ font-size: 13.5px !important;
371
+ font-weight: 600 !important;
372
+ line-height: 1.4 !important;
373
+ color: var(--node-text) !important;
374
+ background: var(--node-bg) !important;
375
+ border: 1.5px solid var(--node-border) !important;
376
+ border-radius: 8px !important;
377
+ padding: 6px 14px !important;
378
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04) !important;
379
+ display: inline-flex !important;
380
+ align-items: center !important;
381
+ white-space: nowrap !important;
382
+ transition: all 0.2s ease !important;
383
+ }
384
+ .markmap-node[data-depth="0"] .markmap-foreign {
385
+ background: var(--root-bg) !important;
386
+ color: var(--root-text) !important;
387
+ border-color: var(--root-border) !important;
388
+ font-size: 16px !important;
389
+ font-weight: 800 !important;
390
+ border-radius: 10px !important;
391
+ padding: 8px 18px !important;
392
+ box-shadow: 0 4px 14px rgba(79, 70, 229, 0.35) !important;
393
+ }
394
+ .markmap-node[data-depth="1"] .markmap-foreign {
395
+ font-weight: 700 !important;
396
+ border-width: 1.8px !important;
397
+ }
398
+ .markmap-link {
399
+ fill: none !important;
400
+ stroke: var(--link-stroke) !important;
401
+ stroke-width: 2px !important;
402
+ stroke-linecap: round !important;
403
+ }
404
+ .markmap-node > circle {
405
+ fill: var(--root-bg) !important;
406
+ stroke: var(--root-border) !important;
407
+ stroke-width: 2px !important;
408
+ r: 6 !important;
409
+ cursor: pointer !important;
410
+ }
411
+ .help-hint {
412
+ position: absolute; bottom: 12px; right: 14px;
413
+ font-size: 11px; color: var(--text-muted); pointer-events: none;
414
+ background: rgba(255, 255, 255, 0.75); padding: 3px 8px; border-radius: 6px;
415
+ border: 1px solid rgba(0,0,0,0.05);
416
+ }
417
+ body.theme-dark .help-hint {
418
+ background: rgba(18, 24, 38, 0.7);
419
+ border-color: rgba(255, 255, 255, 0.08);
420
+ }
421
+ </style>
422
+ </head>
423
+ <body>
424
+ <div class="header">
425
+ <span class="badge">🧠 ANTRI Interactive Mind Map</span>
426
+ <h1>${artifact.title}</h1>
427
+ </div>
428
+ <div class="toolbar">
429
+ <button class="tool-btn" onclick="zoomIn()">🔍 Zoom +</button>
430
+ <button class="tool-btn" onclick="zoomOut()">🔍 Zoom -</button>
431
+ <button class="tool-btn" onclick="fitView()">⛶ Fit / Reset</button>
432
+ <button class="tool-btn" onclick="toggleTheme()" id="themeToggleBtn">🌙 Poster Dark</button>
433
+ <button class="tool-btn" onclick="copySource()">📋 Copy Code</button>
434
+ </div>
435
+ <div class="viewer-card" id="viewerCard">
436
+ <svg id="mindmapSvg"></svg>
437
+ <div class="help-hint">🖱️ Drag to pan · Scroll to zoom · Click nodes with circle handles to collapse/expand</div>
438
+ </div>
439
+ <script>
440
+ const rawContent = ${JSON.stringify(artifact.content.trim())};
441
+
442
+ function parseToMarkdown(content) {
443
+ if (content.startsWith('#') || content.startsWith('-') || content.includes('\\n#') || content.includes('\\n-')) {
444
+ return content;
445
+ }
446
+ const lines = content.split('\\n');
447
+ const mdLines = [];
448
+ let baseIndent = -1;
449
+ for (let line of lines) {
450
+ const trimmed = line.trim();
451
+ if (!trimmed || trimmed === 'mindmap') continue;
452
+ const indent = line.search(/\\S/);
453
+ if (baseIndent === -1) baseIndent = indent;
454
+ const rel = Math.max(0, indent - baseIndent);
455
+ const depth = Math.floor(rel / 2);
456
+
457
+ let clean = trimmed
458
+ .replace(/^root\\(\\((.*?)\\)\\)$/, '$1')
459
+ .replace(/^\\(\\((.*?)\\)\\)$/, '$1')
460
+ .replace(/^\\[(.*?)\\]$/, '$1')
461
+ .replace(/^\\((.*?)\\)$/, '$1')
462
+ .replace(/^\\)\\)(.*?)\\(\\($/, '$1')
463
+ .replace(/^\\)(.*?)\\($/, '$1')
464
+ .replace(/^\\{\\{(.*?)\\}\\}/, '$1');
465
+
466
+ if (depth === 0) {
467
+ mdLines.push('# ' + clean);
468
+ } else if (depth === 1) {
469
+ mdLines.push('## ' + clean);
470
+ } else {
471
+ mdLines.push(' '.repeat(depth - 1) + '- ' + clean);
472
+ }
473
+ }
474
+ return mdLines.join('\\n');
475
+ }
476
+
477
+ const markdownText = parseToMarkdown(rawContent);
478
+ let mmInstance = null;
479
+
480
+ function renderMarkmap() {
481
+ try {
482
+ if (window.markmap && window.markmap.Transformer) {
483
+ const { Transformer, Markmap } = window.markmap;
484
+ const transformer = new Transformer();
485
+ const { root } = transformer.transform(markdownText);
486
+ const svgEl = document.getElementById('mindmapSvg');
487
+ svgEl.innerHTML = '';
488
+ mmInstance = Markmap.create(svgEl, {
489
+ autoFit: true,
490
+ duration: 250,
491
+ nodeMinHeight: 28,
492
+ spacingVertical: 14,
493
+ spacingHorizontal: 85,
494
+ paddingX: 16
495
+ }, root);
496
+ return;
497
+ }
498
+ } catch (err) {
499
+ console.warn('Markmap load error:', err);
500
+ }
501
+ renderFallbackTree();
502
+ }
503
+
504
+ function renderFallbackTree() {
505
+ const svg = document.getElementById('mindmapSvg');
506
+ svg.innerHTML = '';
507
+ const lines = markdownText.split('\\n').filter(l => l.trim().length > 0);
508
+ const rootNode = { name: '${artifact.title}', children: [] };
509
+ const stack = [{ node: rootNode, depth: 0 }];
510
+
511
+ lines.forEach((line) => {
512
+ let depth = 1;
513
+ let text = line.trim();
514
+ if (text.startsWith('# ')) {
515
+ rootNode.name = text.replace('# ', '').trim();
516
+ return;
517
+ } else if (text.startsWith('## ')) {
518
+ depth = 1;
519
+ text = text.replace('## ', '').trim();
520
+ } else {
521
+ const leadSpaces = line.search(/\\S/);
522
+ depth = 2 + Math.floor(leadSpaces / 2);
523
+ text = text.replace(/^[-*]\\s*/, '').trim();
524
+ }
525
+ const newNode = { name: text, children: [] };
526
+ while (stack.length > depth) stack.pop();
527
+ stack[stack.length - 1].node.children.push(newNode);
528
+ stack.push({ node: newNode, depth: depth });
529
+ });
530
+
531
+ let curY = 40;
532
+ function layout(node, depth = 0) {
533
+ node.depth = depth;
534
+ node.x = 60 + depth * 220;
535
+ if (!node.children || node.children.length === 0) {
536
+ node.y = curY;
537
+ curY += 46;
538
+ } else {
539
+ node.children.forEach(c => layout(c, depth + 1));
540
+ const firstY = node.children[0].y;
541
+ const lastY = node.children[node.children.length - 1].y;
542
+ node.y = (firstY + lastY) / 2;
543
+ }
544
+ }
545
+ layout(rootNode);
546
+
547
+ const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
548
+ g.setAttribute('id', 'treeGroup');
549
+ svg.appendChild(g);
550
+
551
+ function draw(node) {
552
+ if (node.children) {
553
+ node.children.forEach(c => {
554
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
555
+ const x1 = node.x + 130, y1 = node.y;
556
+ const x2 = c.x, y2 = c.y;
557
+ const dx = (x2 - x1) * 0.5;
558
+ path.setAttribute('d', 'M ' + x1 + ' ' + y1 + ' C ' + (x1 + dx) + ' ' + y1 + ', ' + (x2 - dx) + ' ' + y2 + ', ' + x2 + ' ' + y2);
559
+ path.setAttribute('class', 'markmap-link');
560
+ g.appendChild(path);
561
+ draw(c);
562
+ });
563
+ }
564
+ const foreign = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
565
+ foreign.setAttribute('x', node.x - 10);
566
+ foreign.setAttribute('y', node.y - 18);
567
+ foreign.setAttribute('width', '200');
568
+ foreign.setAttribute('height', '40');
569
+ foreign.innerHTML = '<div class="markmap-node" data-depth="' + node.depth + '"><div class="markmap-foreign">' + node.name + '</div></div>';
570
+ g.appendChild(foreign);
571
+ }
572
+ draw(rootNode);
573
+ svg.setAttribute('viewBox', '0 0 ' + Math.max(800, curY * 1.8) + ' ' + Math.max(500, curY + 60));
574
+ }
575
+
576
+ function zoomIn() {
577
+ if (mmInstance) mmInstance.rescale(1.25);
578
+ }
579
+ function zoomOut() {
580
+ if (mmInstance) mmInstance.rescale(0.8);
581
+ }
582
+ function fitView() {
583
+ if (mmInstance) mmInstance.fit();
584
+ }
585
+ function toggleTheme() {
586
+ document.body.classList.toggle('theme-dark');
587
+ const isDark = document.body.classList.contains('theme-dark');
588
+ const btn = document.getElementById('themeToggleBtn');
589
+ if (btn) btn.textContent = isDark ? '☀️ Light Mode' : '🌙 Poster Dark';
590
+ }
591
+ function copySource() {
592
+ navigator.clipboard.writeText(rawContent).then(() => {
593
+ alert('Mind map source copied to clipboard!');
594
+ });
595
+ }
596
+
597
+ window.addEventListener('load', () => {
598
+ setTimeout(renderMarkmap, 60);
599
+ });
600
+ if (document.readyState === 'complete') renderMarkmap();
601
+ </script>
602
+ </body>
603
+ </html>`;
604
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
605
+ res.end(htmlMindmap);
606
+ }
267
607
  else {
268
- const isMindmap = artifact.type === 'mindmap';
269
- const badgeLabel = isMindmap ? '🧠 Interactive Mind Map' : '📊 Architecture Graph';
270
- const badgeColor = isMindmap ? '#c084fc' : '#38bdf8';
271
- const badgeBg = isMindmap ? 'rgba(168, 85, 247, 0.15)' : 'rgba(56, 189, 248, 0.15)';
272
- const badgeBorder = isMindmap ? 'rgba(168, 85, 247, 0.3)' : 'rgba(56, 189, 248, 0.3)';
608
+ const badgeLabel = '📊 Architecture Graph';
273
609
  const htmlGraph = `<!DOCTYPE html>
274
610
  <html lang="en">
275
611
  <head>
@@ -279,22 +615,53 @@ export class DesktopServer {
279
615
  <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
280
616
  <style>
281
617
  * { box-sizing: border-box; margin: 0; padding: 0; }
618
+ :root {
619
+ --bg-page: radial-gradient(circle at 15% 15%, rgba(99, 102, 241, 0.08) 0%, transparent 40%), radial-gradient(circle at 85% 85%, rgba(236, 72, 153, 0.08) 0%, transparent 40%), #f8fafc;
620
+ --bg-viewport: #ffffff;
621
+ --border-viewport: rgba(226, 232, 240, 0.9);
622
+ --text-main: #0f172a;
623
+ --text-muted: #64748b;
624
+ --btn-bg: #ffffff;
625
+ --btn-border: #e2e8f0;
626
+ --btn-text: #1e293b;
627
+ --btn-hover: #f1f5f9;
628
+ --shadow-viewport: 0 20px 45px -15px rgba(0, 0, 0, 0.06), 0 0 1px 1px rgba(0, 0, 0, 0.04);
629
+ --badge-bg: rgba(99, 102, 241, 0.1);
630
+ --badge-color: #4f46e5;
631
+ --badge-border: rgba(99, 102, 241, 0.25);
632
+ }
633
+ body.theme-dark {
634
+ --bg-page: radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.25) 0%, transparent 50%), radial-gradient(circle at 90% 80%, rgba(236, 72, 153, 0.22) 0%, transparent 50%), radial-gradient(circle at 50% 50%, rgba(6, 182, 212, 0.18) 0%, transparent 60%), #0d1322;
635
+ --bg-viewport: rgba(18, 24, 38, 0.85);
636
+ --border-viewport: rgba(255, 255, 255, 0.1);
637
+ --text-main: #f8fafc;
638
+ --text-muted: #94a3b8;
639
+ --btn-bg: rgba(30, 41, 59, 0.85);
640
+ --btn-border: rgba(255, 255, 255, 0.12);
641
+ --btn-text: #f8fafc;
642
+ --btn-hover: rgba(51, 65, 85, 0.95);
643
+ --shadow-viewport: 0 20px 45px -15px rgba(0, 0, 0, 0.5);
644
+ --badge-bg: rgba(168, 85, 247, 0.2);
645
+ --badge-color: #c084fc;
646
+ --badge-border: rgba(168, 85, 247, 0.35);
647
+ }
282
648
  body {
283
649
  margin: 0; padding: 20px;
284
650
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
285
- background: radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%), radial-gradient(circle at 90% 80%, rgba(168, 85, 247, 0.15) 0%, transparent 40%), #0a0e17;
286
- color: #f8fafc;
651
+ background: var(--bg-page);
652
+ color: var(--text-main);
287
653
  display: flex; flex-direction: column; align-items: center; min-height: 100vh;
288
654
  overflow-x: hidden;
655
+ transition: background 0.3s ease;
289
656
  }
290
- .header { text-align: center; margin-bottom: 16px; }
291
- h1 { font-size: 19px; color: #ffffff; margin: 0 0 6px 0; font-weight: 800; }
657
+ .header { text-align: center; margin-bottom: 14px; }
658
+ h1 { font-size: 19px; color: var(--text-main); margin: 0 0 6px 0; font-weight: 800; letter-spacing: -0.3px; }
292
659
  .badge {
293
660
  font-size: 11px;
294
- background: ${badgeBg};
295
- color: ${badgeColor};
661
+ background: var(--badge-bg);
662
+ color: var(--badge-color);
296
663
  padding: 4px 12px; border-radius: 9999px;
297
- border: 1px solid ${badgeBorder};
664
+ border: 1px solid var(--badge-border);
298
665
  text-transform: uppercase; font-weight: 700; letter-spacing: 0.5px;
299
666
  display: inline-block; margin-bottom: 6px;
300
667
  }
@@ -302,24 +669,43 @@ export class DesktopServer {
302
669
  display: flex; gap: 8px; margin-bottom: 14px; align-items: center; flex-wrap: wrap; justify-content: center;
303
670
  }
304
671
  .tool-btn {
305
- background: rgba(30, 41, 59, 0.8); border: 1px solid rgba(255, 255, 255, 0.1);
306
- color: #f8fafc; padding: 6px 14px; border-radius: 8px; font-size: 12px;
307
- font-weight: 600; cursor: pointer; transition: all 0.15s; display: flex; align-items: center; gap: 6px;
308
- }
309
- .tool-btn:hover { background: rgba(51, 65, 85, 0.95); border-color: rgba(255, 255, 255, 0.25); }
310
- .viewer-box {
311
- background: rgba(18, 24, 38, 0.75); backdrop-filter: blur(16px);
312
- padding: 24px; border-radius: 16px; border: 1px solid rgba(255, 255, 255, 0.08);
313
- box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.5);
314
- width: 100%; max-width: 100%; overflow: auto; display: flex; justify-content: center;
315
- min-height: 480px; position: relative; cursor: grab;
316
- }
317
- .viewer-box:active { cursor: grabbing; }
318
- .mermaid-inner {
319
- width: 100%; text-align: center; transform-origin: center center; transition: transform 0.1s ease-out;
320
- display: flex; justify-content: center;
321
- }
322
- .mermaid-inner svg { max-width: 100%; height: auto; }
672
+ background: var(--btn-bg); border: 1px solid var(--btn-border);
673
+ color: var(--btn-text); padding: 7px 14px; border-radius: 9px; font-size: 12px;
674
+ font-weight: 700; cursor: pointer; transition: all 0.15s; display: flex; align-items: center; gap: 6px;
675
+ box-shadow: 0 2px 4px rgba(0,0,0,0.03);
676
+ }
677
+ .tool-btn:hover { background: var(--btn-hover); transform: translateY(-1px); }
678
+ .tool-btn:active { transform: translateY(0); }
679
+ .viewport {
680
+ width: 100%; max-width: 1150px; height: 72vh; min-height: 520px;
681
+ position: relative; overflow: hidden; border-radius: 18px;
682
+ background: var(--bg-viewport); backdrop-filter: blur(20px);
683
+ border: 1px solid var(--border-viewport);
684
+ box-shadow: var(--shadow-viewport);
685
+ cursor: grab; user-select: none; touch-action: none;
686
+ }
687
+ .viewport.grabbing { cursor: grabbing; }
688
+ .canvas {
689
+ width: 100%; height: 100%;
690
+ display: flex; align-items: center; justify-content: center;
691
+ transform-origin: center center;
692
+ will-change: transform;
693
+ }
694
+ .canvas.animating {
695
+ transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
696
+ }
697
+ .mermaid { width: 100%; text-align: center; }
698
+ .mermaid svg { max-width: 100%; height: auto; }
699
+ .help-hint {
700
+ position: absolute; bottom: 12px; right: 14px;
701
+ font-size: 11px; color: var(--text-muted); pointer-events: none;
702
+ background: rgba(255, 255, 255, 0.75); padding: 3px 8px; border-radius: 6px;
703
+ border: 1px solid rgba(0,0,0,0.05);
704
+ }
705
+ body.theme-dark .help-hint {
706
+ background: rgba(18, 24, 38, 0.7);
707
+ border-color: rgba(255, 255, 255, 0.08);
708
+ }
323
709
  </style>
324
710
  </head>
325
711
  <body>
@@ -331,32 +717,138 @@ export class DesktopServer {
331
717
  <button class="tool-btn" onclick="zoomIn()">🔍 Zoom +</button>
332
718
  <button class="tool-btn" onclick="zoomOut()">🔍 Zoom -</button>
333
719
  <button class="tool-btn" onclick="resetZoom()">⛶ Reset</button>
720
+ <button class="tool-btn" onclick="toggleTheme()" id="themeToggleBtn">🌙 Poster Dark</button>
334
721
  <button class="tool-btn" onclick="copySource()">📋 Copy Code</button>
335
722
  </div>
336
- <div class="viewer-box" id="viewerBox">
337
- <div class="mermaid-inner" id="mermaidWrap">
723
+ <div class="viewport" id="viewport">
724
+ <div class="canvas" id="canvas">
338
725
  <pre class="mermaid" id="mermaidCode">
339
726
  ${artifact.content.trim()}
340
727
  </pre>
341
728
  </div>
729
+ <div class="help-hint">🖱️ Drag to pan · Scroll to zoom</div>
342
730
  </div>
343
731
  <script>
344
732
  let scale = 1.0;
345
- const wrap = document.getElementById('mermaidWrap');
733
+ let posX = 0;
734
+ let posY = 0;
735
+ let isDragging = false;
736
+ let startPointerX = 0;
737
+ let startPointerY = 0;
738
+ let startPosX = 0;
739
+ let startPosY = 0;
740
+
741
+ const activeTouches = new Map();
742
+ let initialPinchDist = 0;
743
+ let initialPinchScale = 1.0;
744
+
745
+ const viewport = document.getElementById('viewport');
746
+ const canvas = document.getElementById('canvas');
747
+
748
+ function updateTransform(animate = false) {
749
+ if (animate) {
750
+ canvas.classList.add('animating');
751
+ setTimeout(() => canvas.classList.remove('animating'), 320);
752
+ } else {
753
+ canvas.classList.remove('animating');
754
+ }
755
+ canvas.style.transform = 'translate3d(' + posX + 'px, ' + posY + 'px, 0) scale(' + scale + ')';
756
+ }
757
+
758
+ viewport.addEventListener('pointerdown', (e) => {
759
+ activeTouches.set(e.pointerId, { x: e.clientX, y: e.clientY });
760
+ try { viewport.setPointerCapture(e.pointerId); } catch {}
761
+
762
+ if (activeTouches.size === 1) {
763
+ isDragging = true;
764
+ viewport.classList.add('grabbing');
765
+ startPointerX = e.clientX;
766
+ startPointerY = e.clientY;
767
+ startPosX = posX;
768
+ startPosY = posY;
769
+ } else if (activeTouches.size === 2) {
770
+ isDragging = false;
771
+ viewport.classList.remove('grabbing');
772
+ const pts = Array.from(activeTouches.values());
773
+ initialPinchDist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
774
+ initialPinchScale = scale;
775
+ }
776
+ });
777
+
778
+ viewport.addEventListener('pointermove', (e) => {
779
+ if (!activeTouches.has(e.pointerId)) return;
780
+ activeTouches.set(e.pointerId, { x: e.clientX, y: e.clientY });
781
+
782
+ if (activeTouches.size === 1 && isDragging) {
783
+ const dx = e.clientX - startPointerX;
784
+ const dy = e.clientY - startPointerY;
785
+ posX = startPosX + dx;
786
+ posY = startPosY + dy;
787
+ updateTransform(false);
788
+ } else if (activeTouches.size === 2) {
789
+ const pts = Array.from(activeTouches.values());
790
+ const currentDist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
791
+ if (initialPinchDist > 0) {
792
+ scale = Math.min(Math.max(initialPinchScale * (currentDist / initialPinchDist), 0.2), 4.0);
793
+ updateTransform(false);
794
+ }
795
+ }
796
+ });
797
+
798
+ function endPointer(e) {
799
+ activeTouches.delete(e.pointerId);
800
+ try { viewport.releasePointerCapture(e.pointerId); } catch {}
801
+ if (activeTouches.size === 0) {
802
+ isDragging = false;
803
+ viewport.classList.remove('grabbing');
804
+ } else if (activeTouches.size === 1) {
805
+ isDragging = true;
806
+ viewport.classList.add('grabbing');
807
+ const remaining = Array.from(activeTouches.values())[0];
808
+ startPointerX = remaining.x;
809
+ startPointerY = remaining.y;
810
+ startPosX = posX;
811
+ startPosY = posY;
812
+ }
813
+ }
814
+
815
+ viewport.addEventListener('pointerup', endPointer);
816
+ viewport.addEventListener('pointercancel', endPointer);
817
+
818
+ viewport.addEventListener('wheel', (e) => {
819
+ e.preventDefault();
820
+ const delta = e.deltaY < 0 ? 1.15 : 0.87;
821
+ const newScale = Math.min(Math.max(scale * delta, 0.2), 4.0);
822
+
823
+ const rect = viewport.getBoundingClientRect();
824
+ const mouseX = e.clientX - rect.left - rect.width / 2;
825
+ const mouseY = e.clientY - rect.top - rect.height / 2;
826
+
827
+ posX = mouseX - (mouseX - posX) * (newScale / scale);
828
+ posY = mouseY - (mouseY - posY) * (newScale / scale);
829
+ scale = newScale;
830
+ updateTransform(false);
831
+ }, { passive: false });
832
+
346
833
  function zoomIn() {
347
- scale = Math.min(scale + 0.2, 3.5);
348
- applyTransform();
834
+ scale = Math.min(scale * 1.25, 4.0);
835
+ updateTransform(true);
349
836
  }
350
837
  function zoomOut() {
351
- scale = Math.max(scale - 0.2, 0.3);
352
- applyTransform();
838
+ scale = Math.max(scale * 0.8, 0.2);
839
+ updateTransform(true);
353
840
  }
354
841
  function resetZoom() {
355
842
  scale = 1.0;
356
- applyTransform();
843
+ posX = 0;
844
+ posY = 0;
845
+ updateTransform(true);
357
846
  }
358
- function applyTransform() {
359
- if (wrap) wrap.style.transform = 'scale(' + scale + ')';
847
+ function toggleTheme() {
848
+ document.body.classList.toggle('theme-dark');
849
+ const isDark = document.body.classList.contains('theme-dark');
850
+ const btn = document.getElementById('themeToggleBtn');
851
+ if (btn) btn.textContent = isDark ? '☀️ Light Mode' : '🌙 Poster Dark';
360
852
  }
361
853
  function copySource() {
362
854
  const code = ${JSON.stringify(artifact.content.trim())};
@@ -364,19 +856,22 @@ ${artifact.content.trim()}
364
856
  alert('Mind map source code copied to clipboard!');
365
857
  });
366
858
  }
859
+
367
860
  mermaid.initialize({
368
861
  startOnLoad: true,
369
- theme: 'dark',
862
+ theme: 'base',
370
863
  securityLevel: 'loose',
371
864
  themeVariables: {
372
- darkMode: true,
373
- background: '#0f172a',
865
+ darkMode: false,
866
+ background: '#ffffff',
374
867
  primaryColor: '#6366f1',
375
- primaryTextColor: '#ffffff',
376
- primaryBorderColor: '#4338ca',
377
- lineColor: '${badgeColor}',
868
+ primaryTextColor: '#0f172a',
869
+ primaryBorderColor: '#4f46e5',
870
+ lineColor: '#6366f1',
378
871
  secondaryColor: '#0ea5e9',
379
- tertiaryColor: '#10b981'
872
+ tertiaryColor: '#10b981',
873
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
874
+ fontSize: '14px'
380
875
  }
381
876
  });
382
877
  </script>