{
  "format": "BTRISK-UPDATE-V1",
  "version": "1.1.1111",
  "released_at": "2026-10-22T22:26:04.961504194+00:00",
  "release_notes": "Bowtie RISK On-Premise Cumulative Security & Feature Update",
  "files": {
    "css/style.css": "/* Bowtie RISK Assessment Platform - Professional Theme */\n:root {\n  --bg-dark-nav: #0b1329;\n  --bg-dark-surface: #111c38;\n  --border-dark: #1e293b;\n  --text-light: #f8fafc;\n  --text-muted: #94a3b8;\n  --text-dark: #0f172a;\n  --bg-canvas: #f8fafc;\n  --bg-sidebar: #ffffff;\n  --border-light: #e2e8f0;\n  --primary-blue: #2563eb;\n  --danger-red: #e00000;\n  --accent-orange: #f97316;\n  --accent-yellow: #eab308;\n  --accent-slate: #334155;\n  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n  --font-mono: 'JetBrains Mono', monospace;\n}\n\n* {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n}\n\nbody, html {\n  width: 100%;\n  height: 100%;\n  overflow-x: hidden;\n  overflow-y: hidden;\n  font-family: var(--font-sans);\n  background-color: var(--bg-dark-nav);\n  color: var(--text-dark);\n  -webkit-font-smoothing: antialiased;\n}\n\n.app-container {\n  display: flex;\n  flex-direction: column;\n  width: 100vw;\n  max-width: 100vw;\n  height: 100vh;\n  height: 100dvh;\n  overflow-x: hidden;\n  overflow-y: hidden;\n  background-color: var(--bg-dark-nav);\n}\n\n/* ==========================================================================\n   TOP NAVIGATION BAR\n   ========================================================================== */\n.top-nav {\n  height: calc(56px + env(safe-area-inset-top, 0px));\n  min-height: calc(56px + env(safe-area-inset-top, 0px));\n  padding-top: env(safe-area-inset-top, 0px);\n  background-color: var(--bg-dark-nav);\n  color: var(--text-light);\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding-left: max(16px, env(safe-area-inset-left, 16px));\n  padding-right: max(16px, env(safe-area-inset-right, 16px));\n  border-bottom: 1px solid var(--border-dark);\n  z-index: 100;\n  user-select: none;\n  box-sizing: border-box;\n}\n\n.nav-left, .nav-center, .nav-right {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n}\n\n.brand-logo {\n  display: flex;\n  align-items: center;\n  cursor: pointer;\n  transition: opacity 0.2s, transform 0.1s;\n  user-select: none;\n}\n.brand-logo:hover {\n  opacity: 0.85;\n}\n.brand-logo:active {\n  transform: scale(0.98);\n}\n.brand-logo:focus-visible {\n  outline: 2px solid #3b82f6;\n  outline-offset: 2px;\n  border-radius: 4px;\n}\n\n.diagram-selector-wrapper {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  margin-left: 12px;\n}\n\n.nav-select {\n  background-color: #17233f;\n  color: #f1f5f9;\n  border: 1px solid var(--border-dark);\n  border-radius: 6px;\n  padding: 6px 12px;\n  font-size: 13px;\n  font-weight: 500;\n  outline: none;\n  cursor: pointer;\n}\n\n.nav-select:focus {\n  border-color: var(--primary-blue);\n}\n\n.btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  font-size: 13px;\n  font-weight: 500;\n  padding: 6px 12px;\n  border-radius: 6px;\n  border: 1px solid transparent;\n  cursor: pointer;\n  transition: all 0.15s ease;\n  user-select: none;\n  background: transparent;\n  color: var(--text-light);\n}\n\n.btn-primary {\n  background-color: var(--primary-blue);\n  color: #ffffff;\n}\n.btn-primary:hover {\n  background-color: #1d4ed8;\n}\n\n.btn-danger {\n  background-color: #dc2626;\n  color: #ffffff;\n}\n.btn-danger:hover {\n  background-color: #b91c1c;\n}\n\n.btn-outline {\n  border-color: #334155;\n  color: #cbd5e1;\n}\n.btn-outline:hover {\n  background-color: rgba(255, 255, 255, 0.08);\n  color: #ffffff;\n}\n\n.btn-secondary {\n  background-color: #e2e8f0;\n  color: #334155;\n}\n.btn-secondary:hover {\n  background-color: #cbd5e1;\n}\n\n.btn-sm {\n  padding: 4px 10px;\n  font-size: 12px;\n}\n\n.btn-xs {\n  padding: 3px 8px;\n  font-size: 11px;\n}\n\n.btn-icon {\n  padding: 6px;\n  border-radius: 6px;\n  color: #94a3b8;\n}\n.btn-icon:hover {\n  background-color: rgba(255, 255, 255, 0.1);\n  color: #ffffff;\n}\n\n.btn-nav {\n  padding: 6px 10px;\n  color: #cbd5e1;\n  background-color: rgba(255, 255, 255, 0.05);\n  border: 1px solid rgba(255, 255, 255, 0.1);\n}\n.btn-nav:hover {\n  background-color: rgba(255, 255, 255, 0.12);\n  color: #ffffff;\n}\n.btn-nav.active {\n  background-color: rgba(37, 99, 235, 0.35) !important;\n  color: #93c5fd !important;\n  border-color: rgba(96, 165, 250, 0.6) !important;\n}\n\n.btn-profile {\n  padding: 4px 10px 4px 6px;\n  background-color: rgba(255, 255, 255, 0.08);\n  border-radius: 20px;\n  border: 1px solid rgba(255, 255, 255, 0.15);\n  color: #ffffff;\n}\n.btn-profile:hover {\n  background-color: rgba(255, 255, 255, 0.15);\n}\n\n.avatar {\n  width: 26px;\n  height: 26px;\n  border-radius: 50%;\n  background: linear-gradient(135deg, #f97316, #e00000);\n  color: #ffffff;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 11px;\n  font-weight: 700;\n}\n\n.user-email-label {\n  font-size: 12px;\n  font-weight: 500;\n  max-width: 140px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.badge {\n  background-color: #3b82f6;\n  color: #ffffff;\n  font-size: 10px;\n  font-weight: 700;\n  padding: 1px 6px;\n  border-radius: 10px;\n  margin-left: 2px;\n}\n\n.zoom-controls {\n  background-color: #17233f;\n  border: 1px solid var(--border-dark);\n  border-radius: 6px;\n  padding: 2px;\n  display: flex;\n  align-items: center;\n}\n\n.zoom-indicator {\n  font-family: var(--font-mono);\n  font-size: 12px;\n  color: #94a3b8;\n  padding: 0 8px;\n  min-width: 44px;\n  text-align: center;\n}\n\n.save-status {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  font-size: 12px;\n  color: #94a3b8;\n  padding: 4px 6px;\n  width: 140px;\n  min-width: 140px;\n  max-width: 140px;\n  flex-shrink: 0;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.badge-read-only {\n  display: inline-flex;\n  align-items: center;\n  gap: 5px;\n  font-size: 11px;\n  font-weight: 700;\n  padding: 3px 8px;\n  border-radius: 12px;\n  background: rgba(148, 163, 184, 0.15);\n  color: #cbd5e1;\n  border: 1px solid rgba(148, 163, 184, 0.3);\n  letter-spacing: 0.3px;\n  user-select: none;\n  flex-shrink: 0;\n}\n\n.status-dot {\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n}\n.status-dot.saved {\n  background-color: #10b981;\n  box-shadow: 0 0 8px rgba(16, 185, 129, 0.6);\n}\n.status-dot.saving {\n  background-color: #f59e0b;\n  animation: pulse 1s infinite alternate;\n}\n\n@keyframes pulse {\n  from { opacity: 0.4; }\n  to { opacity: 1; }\n}\n\n/* Dropdown Menu */\n.dropdown {\n  position: relative;\n}\n.dropdown-menu {\n  display: none;\n  position: absolute;\n  top: calc(100% + 4px);\n  right: 0;\n  background-color: #ffffff;\n  border: 1px solid var(--border-light);\n  border-radius: 8px;\n  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.15), 0 8px 10px -6px rgba(0, 0, 0, 0.1);\n  min-width: 210px;\n  z-index: 10001;\n  padding: 6px 0;\n}\n.dropdown-menu.show {\n  display: block;\n}\n.dropdown-item {\n  width: 100%;\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  padding: 8px 14px;\n  font-size: 13px;\n  color: #334155;\n  background: transparent;\n  border: none;\n  cursor: pointer;\n  text-align: left;\n}\n.dropdown-item:hover {\n  background-color: #f1f5f9;\n  color: var(--primary-blue);\n}\n.dropdown-divider {\n  border: none;\n  border-top: 1px solid var(--border-light);\n  margin: 4px 0;\n}\n\n/* ==========================================================================\n   WORKSPACE LAYOUT (3-PANE)\n   ========================================================================== */\n.workspace-layout {\n  display: flex;\n  flex: 1;\n  width: 100%;\n  max-width: 100vw;\n  height: calc(100vh - 56px);\n  height: calc(100dvh - 56px);\n  position: relative;\n  overflow: hidden;\n}\n\n.sidebar-pane {\n  background-color: var(--bg-sidebar);\n  border-color: var(--border-light);\n  display: flex;\n  flex-direction: column;\n  z-index: 10;\n  transition: width 0.2s ease, transform 0.2s ease;\n  overflow: hidden;\n}\n\n.left-pane {\n  width: 290px;\n  min-width: 290px;\n  border-right: 1px solid var(--border-light);\n}\n\n.right-pane {\n  width: 320px;\n  min-width: 320px;\n  border-left: 1px solid var(--border-light);\n}\n\n.sidebar-pane.collapsed {\n  width: 0 !important;\n  min-width: 0 !important;\n  border: none;\n}\n\n.pane-header {\n  height: 44px;\n  border-bottom: 1px solid var(--border-light);\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 0 12px;\n  background-color: #f8fafc;\n}\n\n.pane-tabs {\n  display: flex;\n  gap: 4px;\n}\n.pane-tab {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  padding: 6px 10px;\n  font-size: 12px;\n  font-weight: 600;\n  color: #64748b;\n  background: transparent;\n  border: none;\n  border-radius: 4px;\n  cursor: pointer;\n}\n.pane-tab:hover {\n  color: #0f172a;\n}\n.pane-tab.active {\n  background-color: #ffffff;\n  color: var(--primary-blue);\n  box-shadow: 0 1px 3px rgba(0,0,0,0.06);\n}\n\n.btn-toggle-pane {\n  background: transparent;\n  border: none;\n  color: #94a3b8;\n  font-size: 16px;\n  cursor: pointer;\n  padding: 4px 6px;\n  border-radius: 4px;\n}\n.btn-toggle-pane:hover {\n  background-color: #e2e8f0;\n  color: #0f172a;\n}\n\n.pane-body {\n  flex: 1;\n  overflow-y: auto;\n  padding: 12px;\n}\n\n.tab-content {\n  display: none;\n}\n.tab-content.active {\n  display: flex;\n  flex-direction: column;\n}\n\n/* Tree Component */\n.tree-search-bar {\n  margin-bottom: 10px;\n}\n.tree-search-bar input {\n  width: 100%;\n  padding: 6px 10px;\n  border: 1px solid var(--border-light);\n  border-radius: 6px;\n  font-size: 12px;\n  outline: none;\n}\n.tree-search-bar input:focus {\n  border-color: var(--primary-blue);\n}\n\n.tree-container {\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n  font-size: 12px;\n}\n\n.tree-item {\n  display: flex;\n  flex-direction: column;\n}\n\n.tree-node-row {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  padding: 5px 8px;\n  border-radius: 4px;\n  cursor: pointer;\n  user-select: none;\n  color: #334155;\n  transition: background-color 0.1s;\n}\n.tree-node-row:hover {\n  background-color: #f1f5f9;\n}\n.tree-node-row.active {\n  background-color: #eff6ff;\n  color: var(--primary-blue);\n  font-weight: 600;\n}\n\n.tree-toggle {\n  width: 14px;\n  height: 14px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 9px;\n  color: #94a3b8;\n  cursor: pointer;\n}\n\n.tree-dot {\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n  flex-shrink: 0;\n}\n\n.tree-label {\n  flex: 1;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.tree-badge {\n  font-size: 10px;\n  color: #94a3b8;\n  background-color: #f1f5f9;\n  padding: 1px 4px;\n  border-radius: 3px;\n}\n\n.tree-children {\n  margin-left: 16px;\n  border-left: 1px dashed #cbd5e1;\n  padding-left: 4px;\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n}\n\n/* Master Elements List */\n.library-header-actions {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  margin-bottom: 12px;\n}\n.global-elements-list {\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n}\n.global-elem-card {\n  border: 1px solid var(--border-light);\n  border-radius: 6px;\n  padding: 8px 10px;\n  background-color: #ffffff;\n  cursor: pointer;\n  transition: border-color 0.15s, box-shadow 0.15s;\n}\n.global-elem-card:hover {\n  border-color: #94a3b8;\n  box-shadow: 0 2px 6px rgba(0,0,0,0.05);\n}\n.global-elem-card-title {\n  font-size: 12px;\n  font-weight: 600;\n  color: #0f172a;\n  display: flex;\n  justify-content: space-between;\n}\n.global-elem-card-meta {\n  font-size: 11px;\n  color: #64748b;\n  margin-top: 4px;\n}\n\n/* ==========================================================================\n   CANVAS VIEWPORT & STAGE\n   ========================================================================== */\n.canvas-viewport {\n  flex: 1;\n  height: 100%;\n  position: relative;\n  background-color: #f8fafc;\n  overflow: hidden;\n  cursor: grab;\n}\n\n.canvas-viewport.panning {\n  cursor: grabbing;\n}\n\n.canvas-stage {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  transform-origin: 0 0;\n  will-change: transform;\n}\n\n.grid-layer {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  pointer-events: none;\n  z-index: 0;\n  overflow: visible !important;\n}\n\n.connections-layer {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 5000px;\n  height: 5000px;\n  pointer-events: none;\n  z-index: 1;\n  overflow: visible !important;\n}\n\nsvg.connections-layer {\n  overflow: visible !important;\n}\n\n.nodes-layer {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 5000px;\n  height: 5000px;\n  z-index: 2;\n  pointer-events: none; /* children enable pointer events */\n  overflow: visible !important;\n}\n\n/* Floating Toolbar */\n.canvas-floating-toolbar {\n  position: absolute;\n  top: 14px;\n  left: 50%;\n  transform: translateX(-50%);\n  background-color: #ffffff;\n  border: 1px solid var(--border-light);\n  border-radius: 8px;\n  padding: 5px 12px;\n  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  z-index: 30;\n  user-select: none;\n  max-width: calc(100vw - 16px);\n  overflow-x: auto;\n  overflow-y: hidden;\n  -webkit-overflow-scrolling: touch;\n  touch-action: pan-x;\n  scrollbar-width: none;\n}\n.canvas-floating-toolbar::-webkit-scrollbar {\n  display: none;\n}\n\n.tool-group {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  flex-shrink: 0;\n}\n\n.tool-btn {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  padding: 5px 9px;\n  font-size: 12px;\n  font-weight: 600;\n  color: #334155;\n  background-color: #f8fafc;\n  border: 1px solid var(--border-light);\n  border-radius: 5px;\n  cursor: pointer;\n  transition: all 0.15s ease;\n  white-space: nowrap;\n  flex-shrink: 0;\n}\n.tool-btn:hover {\n  background-color: #f1f5f9;\n  border-color: #94a3b8;\n  color: #0f172a;\n}\n\n.tool-color-dot {\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n}\n\n.tool-divider {\n  width: 1px;\n  height: 18px;\n  background-color: var(--border-light);\n  flex-shrink: 0;\n}\n\n.tool-info {\n  font-size: 12px;\n  color: #64748b;\n  white-space: nowrap;\n  flex-shrink: 0;\n}\n\n.btn-tool-align, .btn-line-up {\n  color: #2563eb;\n  border-color: #bfdbfe;\n  background-color: #eff6ff;\n  font-weight: 600;\n  display: inline-flex;\n  align-items: center;\n  gap: 5px;\n}\n.btn-tool-align:hover, .btn-line-up:hover {\n  background-color: #dbeafe;\n  border-color: #3b82f6;\n  color: #1d4ed8;\n}\n\n.btn-tool-order {\n  color: #7c3aed;\n  border-color: #ddd6fe;\n  background-color: #f5f3ff;\n  font-weight: 600;\n  display: inline-flex;\n  align-items: center;\n  gap: 5px;\n}\n.btn-tool-order:hover {\n  background-color: #ede9fe;\n  border-color: #8b5cf6;\n  color: #6d28d9;\n}\n\n.canvas-hud {\n  position: absolute;\n  bottom: calc(14px + env(safe-area-inset-bottom, 0px));\n  left: 14px;\n  background-color: rgba(15, 23, 42, 0.88);\n  backdrop-filter: blur(8px);\n  -webkit-backdrop-filter: blur(8px);\n  color: #ffffff;\n  padding: 6px 10px;\n  border-radius: 8px;\n  border: 1px solid rgba(255, 255, 255, 0.12);\n  font-size: 11px;\n  font-family: var(--font-mono);\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n  z-index: 30;\n  pointer-events: auto;\n  cursor: pointer;\n  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);\n  letter-spacing: 0.3px;\n  line-height: 1.3;\n  user-select: none;\n  transition: all 0.18s ease;\n}\n\n.canvas-hud:hover {\n  background-color: rgba(15, 23, 42, 0.96);\n  border-color: rgba(147, 197, 253, 0.45);\n  transform: translateY(-1px);\n  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);\n}\n\n.canvas-hud:active {\n  transform: translateY(0);\n}\n\n.hud-auto-zoom-pill {\n  margin-top: 3px;\n  padding: 2px 6px;\n  font-size: 9px;\n  font-weight: 700;\n  text-align: center;\n  border-radius: 4px;\n  background: rgba(255, 255, 255, 0.1);\n  color: #94a3b8;\n  cursor: pointer;\n  letter-spacing: 0.4px;\n  text-transform: uppercase;\n  transition: all 0.15s ease;\n  border: 1px solid rgba(255, 255, 255, 0.08);\n}\n\n.hud-auto-zoom-pill:hover {\n  background: rgba(255, 255, 255, 0.22);\n  color: #ffffff;\n}\n\n.hud-auto-zoom-pill.active {\n  background: #2563eb;\n  color: #ffffff;\n  border-color: #3b82f6;\n  box-shadow: 0 0 8px rgba(37, 99, 235, 0.6);\n}\n\n.canvas-hud .hud-row {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  white-space: nowrap;\n}\n\n.canvas-hud .hud-label {\n  color: #94a3b8;\n  font-weight: 500;\n  width: 12px;\n  min-width: 12px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  text-transform: lowercase;\n}\n\n.canvas-hud .hud-icon-label {\n  color: #94a3b8;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.canvas-hud .hud-icon-label svg {\n  display: block;\n}\n\n.canvas-hud #hudX,\n.canvas-hud #hudY {\n  color: #f8fafc;\n  font-weight: 600;\n}\n\n.canvas-hud #hudMag {\n  color: #93c5fd;\n  font-weight: 600;\n}\n\n/* ==========================================================================\n   BOWTIE DIAGRAM NODES (Styled accurately to bowtierisk.com screenshot)\n   ========================================================================== */\n.bowtie-node {\n  position: absolute;\n  pointer-events: auto;\n  background-color: #ffffff;\n  border-radius: 6px;\n  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);\n  display: flex;\n  flex-direction: column;\n  transition: box-shadow 0.15s, border-color 0.15s;\n  cursor: grab;\n  user-select: none;\n  box-sizing: border-box;\n  overflow: hidden;\n  background-clip: padding-box;\n}\n\n.bowtie-node.node-top-event {\n  overflow: visible;\n}\n\n/* Ensure simulation alert badges float on top of layer and are not clipped inside elements */\n.bowtie-node.node-top-event,\n.bowtie-node.has-sim-badge,\n.bowtie-node:has(.sim-card-badge),\n.bowtie-node.is-sim-active-threat,\n.bowtie-node.is-sim-challenged,\n.bowtie-node.is-sim-holding,\n.bowtie-node.is-sim-outcome-holding,\n.bowtie-node.is-sim-outcome-barrier-active,\n.bowtie-node.is-sim-escalation-pulsing,\n.bowtie-node.is-sim-mitigation-active,\n.bowtie-node.is-sim-breached,\n.bowtie-node.is-sim-contained,\n.bowtie-node.is-sim-topevent-alarm {\n  overflow: visible !important;\n  z-index: 35 !important;\n}\n\n.bowtie-node.dragging {\n  cursor: grabbing;\n  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.18);\n  opacity: 0.95;\n  z-index: 100 !important;\n}\n\n.bowtie-node.active-node {\n  outline: 3px solid #3b82f6;\n  outline-offset: 2px;\n}\n\n/* Node Header */\n.node-header {\n  padding: 4px 8px;\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  color: #ffffff;\n  font-size: 11px;\n  font-weight: 700;\n  letter-spacing: 0.3px;\n  flex-shrink: 0;\n}\n\n.node-title-text {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.node-actions {\n  display: flex;\n  align-items: center;\n  gap: 3px;\n  margin-left: 4px;\n}\n\n.node-btn {\n  width: 16px;\n  height: 16px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background-color: rgba(255, 255, 255, 0.25);\n  color: #ffffff;\n  border: none;\n  border-radius: 3px;\n  font-size: 11px;\n  line-height: 1;\n  cursor: pointer;\n  padding: 0;\n}\n.node-btn:hover {\n  background-color: rgba(255, 255, 255, 0.45);\n}\n\n/* Node Body & Textarea */\n.node-body {\n  flex: 1;\n  padding: 4px 6px;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  border-bottom-left-radius: 4px;\n  border-bottom-right-radius: 4px;\n  background-color: #ffffff;\n}\n\n.node-textarea {\n  width: 100%;\n  height: 100%;\n  min-height: 0;\n  border: none;\n  resize: none;\n  font-family: var(--font-sans);\n  font-size: 11px;\n  color: #0f172a;\n  background: transparent;\n  outline: none;\n  line-height: 1.3;\n  cursor: text;\n  overflow-y: auto;\n}\n.node-textarea:focus {\n  background-color: #f8fafc;\n}\n.node-textarea::placeholder {\n  color: #94a3b8;\n  opacity: 1;\n  font-style: italic;\n  font-weight: 400;\n}\n#inspectDesc::placeholder {\n  color: #94a3b8;\n  opacity: 1;\n  font-style: italic;\n}\n\n/* SPECIFIC NODE TYPE DESIGNS MATCHING SCREENSHOT */\n/* 1. Hazard: Yellow Border, White Body, Header */\n.node-hazard {\n  border: 2px solid #eab308;\n  border-radius: 6px;\n  text-align: center;\n  background-color: #ffffff;\n}\n.node-hazard .node-header {\n  background-color: transparent;\n  color: #854d0e;\n  border-bottom: 1px solid #fef08a;\n  font-size: 10px;\n  text-transform: uppercase;\n  justify-content: center;\n}\n.node-hazard .node-textarea {\n  text-align: center;\n  font-weight: 500;\n}\n\n/* 2. Top Event: Rounded/Pill shape with Orange Border & Connection Pins */\n.node-top-event {\n  border: 3px solid #f97316;\n  border-radius: 12px;\n  text-align: center;\n  background-color: #ffffff;\n}\n.node-top-event .node-header {\n  background-color: transparent;\n  color: #c2410c;\n  justify-content: center;\n  font-size: 11px;\n  border-top-left-radius: 9px;\n  border-top-right-radius: 9px;\n}\n.node-top-event .node-body {\n  background-color: transparent;\n  border-bottom-left-radius: 9px;\n  border-bottom-right-radius: 9px;\n}\n.node-top-event .node-textarea {\n  text-align: center;\n  font-weight: 500;\n}\n/* Side connector pins for top event matching screenshot */\n.node-pin-left, .node-pin-right {\n  position: absolute;\n  top: 50%;\n  transform: translateY(-50%);\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n  background-color: #ffffff;\n  border: 2px solid #f97316;\n}\n.node-pin-left { left: -5px; }\n.node-pin-right { right: -5px; }\n\n/* 3. Threat: Blue border & blue header */\n.node-threat {\n  border: 2px solid #2563eb;\n  background-color: #2563eb;\n}\n.node-threat .node-header {\n  background-color: #2563eb;\n}\n\n/* 4. Threat Barrier & Outcome Barrier: Slate header, grey border */\n.node-threat-barrier, .node-outcome-barrier {\n  border: 1.5px solid #cbd5e1;\n  background-color: #334155;\n}\n.node-threat-barrier .node-header, .node-outcome-barrier .node-header {\n  background-color: #334155;\n}\n\n/* 5. Outcome: Red border & red header */\n.node-outcome {\n  border: 2px solid #dc2626;\n  background-color: #dc2626;\n}\n.node-outcome .node-header {\n  background-color: #dc2626;\n}\n\n/* 6. Escalation Factor: Yellow border & yellow header */\n.node-escalation-factor {\n  border: 2px solid #eab308;\n  background-color: #eab308;\n}\n.node-escalation-factor .node-header {\n  background-color: #eab308;\n  color: #451a03;\n}\n.node-escalation-factor .node-btn {\n  background-color: rgba(0, 0, 0, 0.2);\n}\n\n/* 7. Mitigation: Slate border & slate header */\n.node-mitigation {\n  border: 1.5px solid #94a3b8;\n  background-color: #64748b;\n}\n.node-mitigation .node-header {\n  background-color: #64748b;\n}\n\n/* SVG Connection Lines */\n.connection-wire {\n  fill: none;\n  stroke: #94a3b8;\n  stroke-width: 2px;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n  transition: stroke 0.15s, stroke-width 0.15s;\n}\n.connection-wire:hover {\n  stroke: #3b82f6;\n  stroke-width: 3px;\n}\n.connection-wire.active-branch {\n  stroke: #2563eb;\n  stroke-width: 2.5px;\n}\n\n/* ==========================================================================\n   RIGHT PANE: ELEMENT INSPECTOR\n   ========================================================================== */\n.inspector-title-row {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n.pane-title {\n  font-size: 13px;\n  font-weight: 700;\n  color: #0f172a;\n}\n.node-type-badge {\n  font-size: 10px;\n  font-weight: 700;\n  padding: 2px 7px;\n  border-radius: 4px;\n  background-color: #334155;\n  color: #ffffff;\n  text-transform: uppercase;\n}\n\n.empty-selection {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  height: 240px;\n  color: #94a3b8;\n  gap: 12px;\n}\n.empty-selection p {\n  font-size: 12px;\n  max-width: 200px;\n}\n\n.inspector-form {\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n}\n\n.form-group {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n}\n.form-group label {\n  font-size: 11px;\n  font-weight: 600;\n  color: #475569;\n}\n\n.form-input, .form-textarea, .form-select {\n  width: 100%;\n  padding: 6px 10px;\n  border: 1px solid var(--border-light);\n  border-radius: 6px;\n  font-family: var(--font-sans);\n  font-size: 12px;\n  color: #0f172a;\n  outline: none;\n  background-color: #ffffff;\n}\n.form-input:focus, .form-textarea:focus, .form-select:focus {\n  border-color: var(--primary-blue);\n  box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1);\n}\n\n.form-row {\n  display: flex;\n  gap: 8px;\n}\n.col-half {\n  flex: 1;\n}\n\n/* Global Linkage Box */\n.global-link-section {\n  background-color: #f8fafc;\n  border: 1px solid #e2e8f0;\n  border-radius: 6px;\n  padding: 8px 10px;\n  display: flex;\n  flex-direction: column;\n  gap: 6px;\n}\n.section-badge-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n.sub-label {\n  font-size: 11px;\n  font-weight: 700;\n  color: #334155;\n}\n.status-pill {\n  font-size: 10px;\n  padding: 1px 6px;\n  border-radius: 4px;\n  background-color: #e2e8f0;\n  color: #475569;\n  font-weight: 600;\n}\n.status-pill.linked {\n  background-color: #dbeafe;\n  color: #1e40af;\n}\n.global-actions-row {\n  display: flex;\n  gap: 6px;\n  margin-top: 4px;\n}\n\n.element-permissions-box {\n  border: 1px solid var(--border-light);\n  border-radius: 6px;\n  padding: 8px 10px;\n}\n.permissions-mini-list {\n  margin-top: 6px;\n}\n\n.inspector-actions {\n  display: flex;\n  flex-direction: column;\n  gap: 10px;\n  margin-top: 18px;\n  margin-bottom: 24px;\n  padding-bottom: max(24px, env(safe-area-inset-bottom, 24px));\n}\n\n.inspector-actions .btn {\n  min-height: 44px;\n  padding: 10px 14px;\n  font-size: 13.5px;\n  font-weight: 500;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 8px;\n  touch-action: manipulation;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0.05);\n  cursor: pointer;\n}\n\n/* ==========================================================================\n   MODALS & OVERLAYS\n   ========================================================================== */\n.modal-overlay {\n  display: none;\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100vw;\n  height: 100vh;\n  background-color: rgba(15, 23, 42, 0.6);\n  backdrop-filter: blur(3px);\n  z-index: 1000;\n  align-items: center;\n  justify-content: center;\n}\n.modal-overlay.show {\n  display: flex;\n}\n\n.modal-box {\n  background-color: #ffffff;\n  border-radius: 10px;\n  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2), 0 10px 10px -5px rgba(0, 0, 0, 0.1);\n  width: 520px;\n  max-width: 90vw;\n  max-height: 85vh;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  animation: modalPop 0.15s ease-out;\n}\n.modal-sm { width: 400px; }\n.modal-lg { width: 750px; }\n\n@keyframes modalPop {\n  from { transform: scale(0.96); opacity: 0; }\n  to { transform: scale(1); opacity: 1; }\n}\n\n.modal-header {\n  padding: 14px 18px;\n  border-bottom: 1px solid var(--border-light);\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n.modal-header h3 {\n  font-size: 15px;\n  font-weight: 700;\n  color: #0f172a;\n}\n.modal-close {\n  background: transparent;\n  border: none;\n  font-size: 20px;\n  color: #94a3b8;\n  cursor: pointer;\n  line-height: 1;\n}\n.modal-close:hover {\n  color: #0f172a;\n}\n\n.modal-body {\n  padding: 16px 18px;\n  overflow-y: auto;\n}\n\n#newDiagramForm {\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n}\n\n#btnSubmitNewDiagram {\n  margin-top: 14px;\n}\n\n.modal-footer {\n  padding: 12px 18px;\n  border-top: 1px solid var(--border-light);\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  background-color: #f8fafc;\n}\n\n.flex-spacer { flex: 1; }\n.w-full { width: 100%; }\n\n/* Customization Settings Table */\n.customization-table {\n  width: 100%;\n  border-collapse: collapse;\n}\n.customization-table th, .customization-table td {\n  padding: 10px 8px;\n  border-bottom: 1px solid var(--border-light);\n  font-size: 12px;\n  text-align: left;\n}\n.customization-table th {\n  color: #64748b;\n  font-weight: 600;\n}\n.color-picker-cell {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n.color-input-native {\n  width: 32px;\n  height: 28px;\n  padding: 0;\n  border: 1px solid var(--border-light);\n  border-radius: 4px;\n  cursor: pointer;\n}\n.color-text-input {\n  width: 80px;\n  font-family: var(--font-mono);\n  font-size: 11px;\n}\n\n.node-preview-pill {\n  display: inline-block;\n  padding: 4px 10px;\n  border-radius: 4px;\n  color: #ffffff;\n  font-size: 11px;\n  font-weight: 700;\n}\n\n/* Audit Table */\n.audit-table {\n  width: 100%;\n  border-collapse: collapse;\n}\n.audit-table th, .audit-table td {\n  padding: 8px 10px;\n  border-bottom: 1px solid var(--border-light);\n  font-size: 12px;\n  text-align: left;\n}\n.audit-table th {\n  color: #64748b;\n  font-weight: 600;\n}\n.audit-log-toolbar {\n  display: flex;\n  gap: 8px;\n  margin-bottom: 12px;\n}\n.audit-log-toolbar input {\n  flex: 1;\n  padding: 6px 10px;\n  border: 1px solid var(--border-light);\n  border-radius: 6px;\n  font-size: 12px;\n}\n.audit-log-container {\n  max-height: 400px;\n  overflow-y: auto;\n}\n\n/* Permissions Table */\n.permissions-table {\n  width: 100%;\n  border-collapse: collapse;\n  margin-top: 8px;\n}\n.permissions-table th, .permissions-table td {\n  padding: 8px 10px;\n  border-bottom: 1px solid var(--border-light);\n  font-size: 12px;\n  text-align: left;\n}\n\n.alert-info-box {\n  background-color: #eff6ff;\n  border: 1px solid #bfdbfe;\n  border-radius: 6px;\n  padding: 10px 14px;\n  color: #1e40af;\n}\n\n.radio-group {\n  display: flex;\n  gap: 16px;\n  font-size: 13px;\n  margin-top: 4px;\n}\n.radio-group label {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  cursor: pointer;\n}\n\n/* Typography Utilities */\n.text-muted { color: #64748b; }\n.text-xs { font-size: 11px; }\n.text-sm { font-size: 12px; }\n.mb-3 { margin-bottom: 12px; }\n.mb-4 { margin-bottom: 16px; }\n.mt-3 { margin-top: 12px; }\n.mt-4 { margin-top: 16px; }\n\n/* ==========================================================================\n   MOBILE & TOUCH RESPONSIVE STYLES\n   ========================================================================== */\n.mobile-toggle-btn {\n  display: none;\n}\n\n.mobile-drawer-backdrop {\n  display: none;\n  position: fixed !important;\n  top: calc(52px + env(safe-area-inset-top, 0px)) !important;\n  left: 0 !important;\n  width: 100vw !important;\n  height: calc(100vh - 52px - env(safe-area-inset-top, 0px)) !important;\n  height: calc(100dvh - 52px - env(safe-area-inset-top, 0px)) !important;\n  background-color: rgba(15, 23, 42, 0.5) !important;\n  backdrop-filter: blur(2px);\n  -webkit-backdrop-filter: blur(2px);\n  z-index: 990 !important;\n}\n.mobile-drawer-backdrop.show {\n  display: block;\n}\n\n/* Touch Action on Canvas */\n.canvas-viewport, .canvas-stage, .nodes-layer, .connections-layer, .grid-layer {\n  touch-action: none;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n/* Touch Friendly Node Buttons */\n.node-btn {\n  width: 22px;\n  height: 22px;\n  font-size: 13px;\n}\n\n/* Tablet & Mobile Layout (max-width: 900px) */\n@media (max-width: 900px) {\n  .mobile-toggle-btn {\n    display: inline-flex;\n  }\n\n  /* Compact responsive top navigation bar */\n  .top-nav {\n    height: calc(52px + env(safe-area-inset-top, 0px));\n    min-height: calc(52px + env(safe-area-inset-top, 0px));\n    padding-top: env(safe-area-inset-top, 0px);\n    padding-left: max(6px, env(safe-area-inset-left, 6px));\n    padding-right: max(6px, env(safe-area-inset-right, 6px));\n    padding-bottom: 0;\n    gap: 4px;\n    overflow: visible !important;\n    position: relative;\n    z-index: 1000;\n    width: 100%;\n    max-width: 100vw;\n    box-sizing: border-box;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n  .nav-left {\n    gap: 4px;\n    flex: 1;\n    min-width: 0;\n    display: flex;\n    align-items: center;\n    overflow: visible;\n  }\n  .brand-logo {\n    display: flex !important;\n    align-items: center;\n    flex-shrink: 0;\n    margin-right: 6px;\n    cursor: pointer;\n  }\n  .brand-logo svg {\n    height: 28px;\n    width: auto;\n    max-width: 110px;\n    display: block;\n  }\n  .diagram-nav-controls {\n    gap: 4px;\n    margin-left: auto;\n  }\n  .diagram-nav-controls .save-status,\n  .diagram-nav-controls .nav-control-spacer,\n  .diagram-nav-controls #btnOpenHistory,\n  .diagram-nav-controls .zoom-controls,\n  .diagram-nav-controls #btnAutoBalance,\n  .diagram-nav-controls #btnToggleAutoAlign,\n  .diagram-nav-controls #btnOrderBarriersTime,\n  .diagram-nav-controls #btnOpenCostAnalysis,\n  .diagram-nav-controls #btnToggleSituationRunner,\n  .diagram-nav-controls #btnOpenShare,\n  .diagram-nav-controls .dropdown {\n    display: none !important;\n  }\n  .diagram-nav-controls .history-dropdown-wrapper {\n    position: static !important;\n    display: contents !important;\n  }\n  .diagram-history-dropdown {\n    position: fixed !important;\n    top: 50% !important;\n    left: 50% !important;\n    transform: translate(-50%, -50%) !important;\n    width: 92vw !important;\n    max-width: 380px !important;\n    max-height: 80vh !important;\n    z-index: 10050 !important;\n    box-shadow: 0 20px 45px rgba(0, 0, 0, 0.35) !important;\n  }\n  .btn-mobile-burger {\n    display: inline-flex !important;\n  }\n  .view-mode-tabs {\n    margin-left: 0;\n    padding: 2px;\n    gap: 2px;\n    flex-shrink: 0;\n  }\n  .view-mode-tabs .btn-view-tab {\n    padding: 5px 6px;\n    font-size: 11px;\n    border-radius: 5px;\n  }\n  .view-mode-tabs .btn-view-tab span {\n    display: none;\n  }\n  .diagram-selector-wrapper {\n    margin-left: 2px;\n    gap: 3px;\n    flex: 1;\n    min-width: 0;\n    display: flex;\n    align-items: center;\n    overflow: visible;\n  }\n  .diagram-selector-wrapper select {\n    max-width: 90px;\n    min-width: 55px;\n    font-size: 11px;\n    padding: 4px 4px;\n    flex-shrink: 1;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    overflow: hidden;\n    border-radius: 6px;\n  }\n  .diagram-selector-wrapper .btn-sm {\n    padding: 4px 6px;\n    font-size: 11px;\n    flex-shrink: 0;\n    border-radius: 6px;\n  }\n  .diagram-selector-wrapper #btnAddDiagram span {\n    display: none;\n  }\n\n  .nav-center {\n    display: none !important; /* Hide zoom controls and save status on mobile to free up space */\n  }\n\n  .nav-right {\n    gap: 4px;\n    flex-shrink: 0;\n    display: flex;\n    align-items: center;\n    overflow: visible;\n    position: relative;\n  }\n  .nav-right #btnOpenHistory,\n  .nav-right #btnOpenShare,\n  .nav-right #btnOpenSettings {\n    display: none !important;\n  }\n  .nav-right #btnExportMenu {\n    padding: 5px 6px;\n    border-radius: 6px;\n    flex-shrink: 0;\n  }\n  .nav-right #btnExportMenu span {\n    display: none;\n  }\n  .nav-right #exportDropdown {\n    position: absolute !important;\n    top: calc(100% + 4px) !important;\n    right: 0 !important;\n    left: auto !important;\n    z-index: 10001 !important;\n    min-width: 195px;\n    max-width: calc(100vw - 16px);\n  }\n  .nav-right .collaborators-stack {\n    padding: 2px 4px;\n    gap: 3px;\n    margin-right: 0;\n    flex-shrink: 0;\n  }\n  @media (max-width: 440px) {\n    .nav-right .collaborators-stack {\n      display: none !important;\n    }\n  }\n  .nav-right .btn-profile {\n    padding: 2px;\n    flex-shrink: 0;\n  }\n  .nav-right .btn-profile .user-email-label,\n  .nav-right .btn-profile svg {\n    display: none;\n  }\n  .nav-right .avatar {\n    width: 26px;\n    height: 26px;\n    font-size: 11px;\n  }\n  .nav-right #btnMobileToggleRight {\n    display: inline-flex !important;\n    padding: 5px 6px;\n    border-radius: 6px;\n    flex-shrink: 0;\n  }\n\n  /* Fixed drawer panels on mobile */\n  .left-pane {\n    position: fixed !important;\n    top: calc(52px + env(safe-area-inset-top, 0px)) !important;\n    left: 0 !important;\n    bottom: 0 !important;\n    height: calc(100vh - 52px - env(safe-area-inset-top, 0px)) !important;\n    height: calc(100dvh - 52px - env(safe-area-inset-top, 0px)) !important;\n    width: 320px !important;\n    max-width: 85vw !important;\n    z-index: 1000 !important;\n    background-color: #ffffff !important;\n    box-shadow: 4px 0 24px rgba(0, 0, 0, 0.25) !important;\n    transform: translateX(-100%) !important;\n    transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important;\n    display: flex !important;\n    flex-direction: column !important;\n    overflow: hidden !important;\n  }\n  .left-pane.drawer-open {\n    transform: translateX(0) !important;\n  }\n\n  .right-pane {\n    position: fixed !important;\n    top: calc(52px + env(safe-area-inset-top, 0px)) !important;\n    right: 0 !important;\n    bottom: 0 !important;\n    height: calc(100vh - 52px - env(safe-area-inset-top, 0px)) !important;\n    height: calc(100dvh - 52px - env(safe-area-inset-top, 0px)) !important;\n    width: 340px !important;\n    max-width: 90vw !important;\n    z-index: 1000 !important;\n    background-color: #ffffff !important;\n    box-shadow: -4px 0 24px rgba(0, 0, 0, 0.25) !important;\n    transform: translateX(100%) !important;\n    transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important;\n    display: flex !important;\n    flex-direction: column !important;\n    overflow: hidden !important;\n  }\n  .right-pane.drawer-open {\n    transform: translateX(0) !important;\n  }\n  .right-pane .pane-body {\n    padding-bottom: max(96px, calc(52px + env(safe-area-inset-bottom, 34px))) !important;\n    -webkit-overflow-scrolling: touch;\n  }\n\n  /* Floating toolbar on mobile */\n  .canvas-floating-toolbar {\n    position: absolute;\n    top: 8px;\n    left: 8px;\n    right: 8px;\n    width: auto;\n    max-width: calc(100vw - 16px);\n    transform: none;\n    padding: 4px 8px;\n    gap: 6px;\n    overflow-x: auto;\n    overflow-y: hidden;\n    -webkit-overflow-scrolling: touch;\n    touch-action: pan-x;\n    justify-content: flex-start;\n    scrollbar-width: none;\n  }\n  .canvas-floating-toolbar::-webkit-scrollbar {\n    display: none;\n  }\n  .tool-group {\n    gap: 5px;\n    flex-shrink: 0;\n  }\n  .tool-btn {\n    padding: 5px 8px;\n    font-size: 11px;\n    white-space: nowrap;\n    flex-shrink: 0;\n  }\n  .tool-info {\n    display: none;\n  }\n\n  .modal-box {\n    width: 95vw;\n    max-height: 90vh;\n  }\n  .customization-table-wrap, .permissions-table-wrap, .audit-log-container {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n\n  /* Prevent iOS Safari / mobile browser forced auto-zoom on input focus */\n  input,\n  textarea,\n  select,\n  .node-textarea,\n  .form-input,\n  .form-textarea,\n  .form-select,\n  .sim-select,\n  .tree-search-bar input {\n    font-size: 16px !important;\n  }\n\n  /* Mobile Sidebar Edge Restore Tabs */\n  .sidebar-edge-tab {\n    position: fixed !important;\n    top: calc(138px + env(safe-area-inset-top, 0px)) !important;\n    transform: none !important;\n    width: 24px !important;\n    height: 36px !important;\n    z-index: 950 !important;\n    border-radius: 6px !important;\n    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2) !important;\n  }\n  .sidebar-edge-tab.edge-left {\n    border-top-left-radius: 0 !important;\n    border-bottom-left-radius: 0 !important;\n  }\n  .sidebar-edge-tab.edge-right {\n    border-top-right-radius: 0 !important;\n    border-bottom-right-radius: 0 !important;\n  }\n}\n\n/* Coarse touch devices: ensure inputs are always >= 16px to prevent iOS auto-zoom */\n@media (pointer: coarse) {\n  .node-textarea,\n  .form-input,\n  .form-textarea,\n  .form-select,\n  .sim-select {\n    font-size: 16px !important;\n  }\n}\n\n/* Small phone screens (max-width: 500px) */\n@media (max-width: 500px) {\n  .top-nav {\n    padding-left: max(4px, env(safe-area-inset-left, 4px));\n    padding-right: max(4px, env(safe-area-inset-right, 4px));\n    gap: 3px;\n    overflow: visible !important;\n  }\n  .diagram-selector-wrapper select {\n    max-width: 80px;\n    min-width: 50px;\n    font-size: 11px;\n    padding: 3px 3px;\n  }\n  .btn-profile {\n    padding: 1px;\n  }\n}\n\n/* ==========================================================================\n   VIEW MODE SWITCHER (DOCUMENTS vs CANVAS)\n   ========================================================================== */\n.view-mode-tabs {\n  display: flex;\n  align-items: center;\n  background: rgba(255, 255, 255, 0.08);\n  padding: 3px;\n  border-radius: 8px;\n  gap: 2px;\n  margin-left: 8px;\n}\n\n.btn-view-tab {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  padding: 5px 12px;\n  border-radius: 6px;\n  font-size: 12px;\n  font-weight: 500;\n  color: #94a3b8;\n  border: none;\n  background: transparent;\n  cursor: pointer;\n  transition: all 0.15s ease;\n}\n\n.btn-view-tab:hover {\n  color: #ffffff;\n}\n\n.btn-view-tab.active {\n  background: var(--primary-blue);\n  color: #ffffff;\n  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);\n}\n\n/* ==========================================================================\n   USER PROFILE DROPDOWN MENU\n   ========================================================================== */\n.user-profile-wrapper {\n  position: relative;\n}\n\n.user-profile-dropdown {\n  position: absolute;\n  top: 100%;\n  right: 0;\n  margin-top: 8px;\n  width: 280px;\n  background: #ffffff;\n  border: 1px solid var(--border-light);\n  border-radius: 10px;\n  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15);\n  padding: 12px;\n  z-index: 1000;\n  display: none;\n}\n\n.user-profile-dropdown.show {\n  display: block;\n  animation: fadeInDown 0.15s ease;\n}\n\n.user-profile-header {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding-bottom: 12px;\n}\n\n.avatar-lg {\n  width: 56px;\n  height: 56px;\n  min-width: 56px;\n  min-height: 56px;\n  max-width: 56px;\n  max-height: 56px;\n  aspect-ratio: 1 / 1 !important;\n  border-radius: 50% !important;\n  background: linear-gradient(135deg, #2563eb, #1d4ed8);\n  color: #ffffff;\n  font-weight: 600;\n  font-size: 18px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0 !important;\n  overflow: hidden !important;\n}\n\n.user-profile-details {\n  flex: 1;\n  overflow: hidden;\n}\n\n.user-profile-name {\n  font-weight: 600;\n  font-size: 14px;\n  color: #0f172a;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.user-profile-email {\n  font-size: 12px;\n  color: #64748b;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  margin-bottom: 4px;\n}\n\n.user-profile-meta {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 4px;\n}\n\n.user-profile-meta .badge {\n  font-size: 10px;\n  padding: 2px 6px;\n  background: #f1f5f9;\n  color: #475569;\n}\n\n/* ==========================================================================\n   AUTH TABS (SIGN IN & SIGN UP)\n   ========================================================================== */\n.auth-tabs {\n  display: flex;\n  border-bottom: 2px solid var(--border-light);\n  margin-bottom: 18px;\n}\n\n.auth-tab {\n  flex: 1;\n  padding: 10px 0;\n  font-size: 13px;\n  font-weight: 600;\n  text-align: center;\n  background: transparent;\n  border: none;\n  border-bottom: 2px solid transparent;\n  margin-bottom: -2px;\n  color: #64748b;\n  cursor: pointer;\n  transition: all 0.2s;\n}\n\n.auth-tab:hover {\n  color: var(--primary-blue);\n}\n\n.auth-tab.active {\n  color: var(--primary-blue);\n  border-bottom-color: var(--primary-blue);\n}\n\n.auth-form-pane {\n  display: none;\n}\n\n.auth-form-pane.active {\n  display: block;\n}\n\n/* ==========================================================================\n   VERSION REVERT BUTTON\n   ========================================================================== */\n.btn-revert-version {\n  font-size: 11px;\n  padding: 3px 8px;\n  border-color: #2563eb;\n  color: #2563eb;\n  background: transparent;\n  transition: all 0.15s ease;\n}\n\n.btn-revert-version:hover {\n  background: #2563eb;\n  color: #ffffff;\n}\n\n/* ==========================================================================\n   GOOGLE DRIVE STYLE DOCUMENTS LANDING PAGE\n   ========================================================================== */\n.documents-landing-view {\n  display: flex;\n  flex: 1;\n  width: 100%;\n  max-width: 100vw;\n  height: calc(100vh - 56px);\n  height: calc(100dvh - 56px);\n  overflow: hidden;\n  background: #f8fafc;\n}\n\n/* Drive Sidebar */\n.drive-sidebar {\n  width: 250px;\n  min-width: 250px;\n  background: #ffffff;\n  border-right: 1px solid var(--border-light);\n  display: flex;\n  flex-direction: column;\n  padding: 20px 14px;\n  gap: 20px;\n}\n\n.drive-sidebar-header {\n  padding: 0 8px;\n}\n\n.drive-sidebar-title {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  font-size: 16px;\n  font-weight: 700;\n  color: #0f172a;\n}\n\n.drive-nav-list {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n}\n\n.drive-nav-item {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 10px 14px;\n  border-radius: 8px;\n  border: none;\n  background: transparent;\n  color: #475569;\n  font-size: 13px;\n  font-weight: 500;\n  cursor: pointer;\n  text-align: left;\n  transition: all 0.15s ease;\n}\n\n.drive-nav-item:hover {\n  background: #f1f5f9;\n  color: #0f172a;\n}\n\n.drive-nav-item.active {\n  background: #eff6ff;\n  color: var(--primary-blue);\n  font-weight: 600;\n}\n\n.drive-storage-stats {\n  margin-top: auto;\n  padding: 14px;\n  background: #f8fafc;\n  border: 1px solid var(--border-light);\n  border-radius: 8px;\n}\n\n.drive-storage-stats .stats-label {\n  font-size: 11px;\n  text-transform: uppercase;\n  font-weight: 600;\n  color: #94a3b8;\n  margin-bottom: 6px;\n  letter-spacing: 0.5px;\n}\n\n.drive-storage-stats .stats-row {\n  display: flex;\n  justify-content: space-between;\n  font-size: 12px;\n  font-weight: 600;\n  color: #334155;\n}\n\n.drive-app-version {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  padding: 6px 10px;\n  margin-top: 10px;\n  font-size: 11px;\n  font-weight: 500;\n  color: #64748b;\n  letter-spacing: 0.3px;\n  user-select: none;\n  cursor: pointer;\n  border-radius: 6px;\n  background: #f8fafc;\n  border: 1px solid var(--border-light);\n  transition: all 0.15s ease;\n}\n\n.drive-app-version:hover {\n  background: #f1f5f9;\n  color: #1e293b;\n  border-color: #cbd5e1;\n}\n\n.drive-app-version .version-dot {\n  width: 6px;\n  height: 6px;\n  border-radius: 50%;\n  background: #10b981;\n  box-shadow: 0 0 6px rgba(16, 185, 129, 0.5);\n  flex-shrink: 0;\n}\n\n.drive-app-version .version-text {\n  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n  font-size: 11.5px;\n  font-weight: 600;\n  color: #475569;\n}\n\n/* Mobile version tag (hidden by default on desktop) */\n.drive-mobile-version {\n  display: none;\n}\n\n/* Drive Main Content */\n.drive-main-content {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  overflow-y: auto;\n  padding: 24px 32px;\n  gap: 24px;\n}\n\n/* Drive Toolbar */\n.drive-toolbar {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: 14px;\n  width: 100%;\n  padding-bottom: 16px;\n  border-bottom: 1px solid var(--border-light);\n}\n\n.drive-toolbar-search-row {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 10px;\n  width: 100%;\n  max-width: 560px;\n  margin: 0 auto;\n}\n\n.drive-toolbar-search-row .drive-search-box {\n  flex: 1;\n  max-width: 500px;\n}\n\n.drive-toolbar-actions-row {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 12px;\n  width: 100%;\n  flex-wrap: wrap;\n}\n\n.drive-toolbar-breadcrumb-row {\n  display: flex;\n  align-items: center;\n  justify-content: flex-start;\n  width: 100%;\n  padding-top: 2px;\n}\n\n.drive-toolbar-left,\n.drive-toolbar-actions {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n}\n\n.drive-breadcrumbs {\n  display: inline-flex;\n  align-items: center;\n  justify-content: flex-start;\n  font-size: 14px;\n  font-weight: 500;\n  color: #64748b;\n  gap: 4px;\n  margin-left: 0;\n  text-align: left;\n  flex-wrap: wrap;\n}\n\n.drive-breadcrumbs .breadcrumb-item {\n  cursor: pointer;\n  padding: 4px 6px;\n  border-radius: 4px;\n  transition: background 0.15s;\n  display: inline-flex;\n  align-items: center;\n}\n\n.drive-breadcrumbs .breadcrumb-item:hover {\n  background: #e2e8f0;\n  color: #0f172a;\n}\n\n.drive-breadcrumbs .breadcrumb-item.active {\n  color: #0f172a;\n  font-weight: 600;\n  cursor: default;\n}\n\n.drive-breadcrumbs .breadcrumb-separator {\n  color: #94a3b8;\n}\n\n.drive-search-box {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  background: #ffffff;\n  border: 1px solid var(--border-light);\n  border-radius: 8px;\n  padding: 6px 12px;\n  width: 260px;\n  color: #64748b;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\n}\n\n.drive-search-box input {\n  border: none;\n  background: transparent;\n  outline: none;\n  font-size: 13px;\n  color: #0f172a;\n  width: 100%;\n}\n\n/* Sections */\n.drive-section {\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n}\n\n.drive-section-title {\n  font-size: 15px;\n  font-weight: 700;\n  color: #0f172a;\n  letter-spacing: -0.2px;\n}\n\n/* Folders Grid */\n.drive-folders-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));\n  gap: 14px;\n  justify-content: center;\n}\n\n.drive-folder-card {\n  background: #ffffff;\n  border: 1.5px solid #cbd5e1;\n  border-radius: 10px;\n  padding: 12px 14px;\n  cursor: pointer;\n  transition: all 0.18s ease;\n  box-shadow: 0 3px 10px rgba(15, 23, 42, 0.07), 0 1px 2px rgba(15, 23, 42, 0.05);\n}\n\n.drive-folder-card:hover {\n  border-color: #3b82f6;\n  box-shadow: 0 8px 20px rgba(37, 99, 235, 0.14), 0 2px 6px rgba(15, 23, 42, 0.06);\n  transform: translateY(-1px);\n}\n\n.folder-card-main {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n}\n\n.folder-icon-wrap {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n}\n\n.folder-meta {\n  flex: 1;\n  overflow: hidden;\n}\n\n.folder-name {\n  font-weight: 600;\n  font-size: 13px;\n  color: #0f172a;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.folder-count {\n  font-size: 11px;\n  color: #64748b;\n}\n\n.folder-actions-btn {\n  padding: 2px 6px;\n  font-size: 16px;\n  color: #94a3b8;\n  border-radius: 4px;\n  cursor: pointer;\n}\n\n.folder-actions-btn:hover {\n  background: #f1f5f9;\n  color: #0f172a;\n}\n\n/* Diagrams Grid - 4:3 Portrait Ratio (4 high : 3 wide) */\n.drive-diagrams-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(240px, 275px));\n  gap: 20px;\n  justify-content: center;\n}\n\n.drive-diagram-card {\n  background: #ffffff;\n  border: 1.5px solid #cbd5e1;\n  border-radius: 12px;\n  overflow: visible;\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  transition: all 0.2s ease;\n  box-shadow: 0 4px 14px rgba(15, 23, 42, 0.08), 0 1px 3px rgba(15, 23, 42, 0.06);\n  aspect-ratio: 3 / 4;\n  max-width: 280px;\n  cursor: pointer;\n}\n\n.drive-diagram-card:hover {\n  border-color: #3b82f6;\n  box-shadow: 0 10px 26px rgba(37, 99, 235, 0.16), 0 4px 10px rgba(15, 23, 42, 0.08);\n  transform: translateY(-2px);\n  z-index: 10;\n}\n\n.drive-diagram-card:has(.card-kebab-menu.show) {\n  z-index: 30;\n}\n\n.diagram-card-preview {\n  height: 155px;\n  background: #f8fafc;\n  border-bottom: 1px solid #cbd5e1;\n  border-top-left-radius: 10.5px;\n  border-top-right-radius: 10.5px;\n  position: relative;\n  overflow: hidden;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.diagram-thumbnail-svg {\n  width: 100%;\n  height: 100%;\n  display: block;\n}\n\n.diagram-card-preview-overlay {\n  position: absolute;\n  top: 8px;\n  left: 8px;\n  display: flex;\n  align-items: center;\n  gap: 5px;\n  pointer-events: none;\n  z-index: 5;\n}\n\n/* Mini Bowtie Graphic Badge */\n.mini-bowtie-graphic {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  color: #2563eb;\n  font-size: 11px;\n  font-weight: 700;\n  letter-spacing: 0.3px;\n  text-transform: uppercase;\n}\n\n.mini-bowtie-graphic svg {\n  flex-shrink: 0;\n}\n\n.card-folder-tag {\n  font-size: 10px;\n  font-weight: 600;\n  padding: 2px 7px;\n  border-radius: 4px;\n  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n  background: #ffffff !important;\n  border: 1px solid #e2e8f0;\n}\n\n.badge-shared {\n  font-size: 10px;\n  padding: 2px 7px;\n  background: #2563eb;\n  color: #ffffff;\n  border-radius: 4px;\n  font-weight: 600;\n  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n\n.diagram-card-body {\n  padding: 12px 14px 10px 14px;\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  gap: 6px;\n  min-height: 0;\n}\n\n.diagram-card-title {\n  font-size: 14px;\n  font-weight: 700;\n  color: #0f172a;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  margin: 0;\n}\n\n.diagram-card-desc {\n  font-size: 11px;\n  color: #64748b;\n  line-height: 1.3;\n  margin: 0;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.diagram-card-keynodes {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  margin-top: 2px;\n}\n\n.keynode-pill {\n  font-size: 11px;\n  padding: 3px 8px;\n  border-radius: 4px;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  font-weight: 500;\n}\n\n.hazard-pill {\n  background: #eff6ff;\n  color: #1d4ed8;\n  border-left: 3px solid #2563eb;\n}\n\n.topevent-pill {\n  background: #fef2f2;\n  color: #b91c1c;\n  border-left: 3px solid #dc2626;\n}\n\n/* Threats, Barriers, Outcomes stats pills snugly positioned under keynodes */\n.diagram-card-stats {\n  display: flex;\n  gap: 6px;\n  flex-wrap: wrap;\n  margin-top: 6px;\n  padding-top: 0;\n}\n\n.stat-pill {\n  font-size: 10.5px;\n  padding: 2px 7px;\n  border-radius: 4px;\n  background: #f1f5f9;\n  border: 1px solid #e2e8f0;\n  color: #334155;\n  font-weight: 600;\n}\n\n.diagram-card-footer {\n  padding: 8px 12px 9px 12px;\n  background: #f8fafc;\n  border-top: 1px solid var(--border-light);\n  border-top-color: #cbd5e1;\n  border-bottom-left-radius: 10.5px;\n  border-bottom-right-radius: 10.5px;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 8px;\n}\n\n.author-meta {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.avatar-sm {\n  width: 24px;\n  height: 24px;\n  border-radius: 50%;\n  background: #334155;\n  color: #ffffff;\n  font-size: 10px;\n  font-weight: 600;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.card-action-btns {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n\n/* Drive List View Table */\n.drive-diagrams-list {\n  background: #ffffff;\n  border: 1px solid var(--border-light);\n  border-radius: 10px;\n  overflow: hidden;\n}\n\n.drive-table {\n  width: 100%;\n  border-collapse: collapse;\n  font-size: 13px;\n  text-align: left;\n}\n\n.drive-table th {\n  background: #f8fafc;\n  padding: 10px 14px;\n  font-weight: 600;\n  color: #475569;\n  border-bottom: 1px solid var(--border-light);\n}\n\n.drive-table td {\n  padding: 12px 14px;\n  border-bottom: 1px solid var(--border-light);\n  color: #334155;\n}\n\n.drive-table tr:hover td {\n  background: #f8fafc;\n}\n\n.table-doc-title {\n  display: flex;\n  align-items: center;\n  color: #0f172a;\n}\n\n.row-actions {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n\n.drive-empty-card {\n  grid-column: 1 / -1;\n  text-align: center;\n  padding: 40px 20px;\n  background: #ffffff;\n  border: 2px dashed var(--border-light);\n  border-radius: 12px;\n  color: #64748b;\n}\n\n.drive-empty-hint {\n  padding: 16px;\n  color: #94a3b8;\n  font-size: 13px;\n  font-style: italic;\n}\n\n/* Folder Context Menu */\n.folder-context-menu {\n  background: #ffffff;\n  border: 1px solid var(--border-light);\n  border-radius: 8px;\n  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);\n  min-width: 190px;\n  padding: 6px 0;\n}\n\n.folder-context-menu .dropdown-item {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  width: 100%;\n  padding: 8px 14px;\n  font-size: 13px;\n  border: none;\n  background: transparent;\n  cursor: pointer;\n  color: #334155;\n  text-align: left;\n}\n\n.folder-context-menu .dropdown-item:hover {\n  background: #f1f5f9;\n  color: #0f172a;\n}\n\n/* Responsive Overrides */\n@media (max-width: 900px) {\n  .documents-landing-view {\n    flex-direction: column;\n    height: calc(100vh - 84px);\n    height: calc(100dvh - 84px);\n  }\n  .drive-sidebar {\n    width: 100%;\n    min-width: 100%;\n    border-right: none;\n    border-bottom: 1px solid var(--border-light);\n    padding: 8px 12px;\n    gap: 8px;\n    flex-shrink: 0;\n  }\n  .drive-nav-list {\n    flex-direction: row;\n    justify-content: flex-start;\n    align-items: center;\n    overflow-x: auto;\n    scrollbar-width: none;\n    -webkit-overflow-scrolling: touch;\n    gap: 6px;\n    width: 100%;\n    padding: 2px 0;\n  }\n  .drive-nav-list::-webkit-scrollbar {\n    display: none;\n  }\n  .drive-nav-item {\n    flex-shrink: 0;\n    white-space: nowrap;\n    padding: 6px 10px;\n    font-size: 12px;\n    gap: 6px;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: 6px;\n  }\n  .drive-nav-item svg {\n    width: 14px;\n    height: 14px;\n    flex-shrink: 0;\n  }\n  .drive-storage-stats {\n    display: none;\n  }\n  .drive-sidebar .drive-app-version {\n    display: none !important;\n  }\n  .drive-mobile-version {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: 8px;\n    padding: 16px 0 calc(16px + env(safe-area-inset-bottom, 0px)) 0;\n    margin-top: auto;\n    font-size: 11px;\n    color: #94a3b8;\n    cursor: pointer;\n    user-select: none;\n    transition: opacity 0.15s ease;\n  }\n  .drive-mobile-version:hover,\n  .drive-mobile-version:active {\n    opacity: 0.8;\n  }\n  .drive-mobile-version .version-dot {\n    width: 6px;\n    height: 6px;\n    border-radius: 50%;\n    background: #10b981;\n    box-shadow: 0 0 6px rgba(16, 185, 129, 0.4);\n    flex-shrink: 0;\n  }\n  .drive-mobile-version .version-text {\n    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n    font-size: 11px;\n    font-weight: 500;\n    color: #64748b;\n  }\n  .drive-main-content {\n    padding: 12px 14px;\n    flex: 1;\n    overflow-y: auto;\n    overflow-x: hidden;\n    -webkit-overflow-scrolling: touch;\n  }\n  .drive-page-header {\n    display: none;\n  }\n  .drive-toolbar {\n    gap: 10px;\n    padding-bottom: 12px;\n  }\n  .drive-toolbar-search-row {\n    width: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: 8px;\n  }\n  .drive-toolbar-search-row .drive-search-box {\n    flex: 1;\n    width: auto;\n    max-width: none;\n    min-width: 0;\n  }\n  .drive-toolbar-actions-row {\n    width: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: 8px;\n    flex-wrap: wrap;\n  }\n  .drive-toolbar-breadcrumb-row {\n    width: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: flex-start;\n  }\n  .drive-breadcrumbs {\n    margin-left: 0;\n    justify-content: flex-start;\n    text-align: left;\n  }\n  .drive-toolbar-left,\n  .drive-toolbar-actions {\n    width: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: 8px;\n    flex-wrap: wrap;\n  }\n  .drive-diagrams-grid {\n    grid-template-columns: minmax(0, 1fr);\n    width: 100%;\n    gap: 16px;\n    justify-content: stretch;\n  }\n  .drive-folders-grid {\n    grid-template-columns: minmax(0, 1fr);\n    width: 100%;\n    gap: 12px;\n    justify-content: stretch;\n  }\n  .drive-diagram-card {\n    width: 100%;\n    max-width: 100%;\n    min-width: 0;\n    box-sizing: border-box;\n    aspect-ratio: auto;\n    min-height: auto;\n    overflow: hidden;\n  }\n  .diagram-card-preview {\n    width: 100%;\n    max-width: 100%;\n    box-sizing: border-box;\n  }\n  .diagram-card-preview-overlay {\n    max-width: calc(100% - 16px);\n    flex-wrap: wrap;\n  }\n  .card-folder-tag {\n    max-width: 180px;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n  .diagram-card-body {\n    min-width: 0;\n    max-width: 100%;\n    box-sizing: border-box;\n    overflow: hidden;\n  }\n  .diagram-card-title,\n  .diagram-card-desc {\n    min-width: 0;\n    max-width: 100%;\n    box-sizing: border-box;\n  }\n  .diagram-card-keynodes {\n    min-width: 0;\n    max-width: 100%;\n    box-sizing: border-box;\n  }\n  .keynode-pill {\n    min-width: 0;\n    max-width: 100%;\n    box-sizing: border-box;\n  }\n  .diagram-card-footer {\n    min-width: 0;\n    max-width: 100%;\n    box-sizing: border-box;\n  }\n}\n\n/* ==========================================================================\n   Real-Time Multi-User Collaboration & Collaborator Avatars (Google Docs Style)\n   ========================================================================== */\n\n.collaborators-stack {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  padding: 3px 9px;\n  background: rgba(255, 255, 255, 0.08);\n  border: 1px solid rgba(255, 255, 255, 0.12);\n  border-radius: 20px;\n  margin-right: 6px;\n  user-select: none;\n  transition: background 0.15s, border-color 0.15s;\n}\n\n.collaborators-stack:hover {\n  background: rgba(255, 255, 255, 0.13);\n  border-color: rgba(255, 255, 255, 0.2);\n}\n\n.collab-pulse-dot {\n  width: 7px;\n  height: 7px;\n  border-radius: 50%;\n  background: #10b981;\n  box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);\n  animation: pulse-green 2s infinite;\n}\n\n@keyframes pulse-green {\n  0% {\n    transform: scale(0.95);\n    box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);\n  }\n  70% {\n    transform: scale(1);\n    box-shadow: 0 0 0 5px rgba(16, 185, 129, 0);\n  }\n  100% {\n    transform: scale(0.95);\n    box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);\n  }\n}\n\n.collab-avatars-list {\n  display: flex;\n  align-items: center;\n  gap: -6px; /* overlapping avatars like Google Docs */\n}\n\n.collab-avatar-chip {\n  width: 24px;\n  height: 24px;\n  border-radius: 50%;\n  background: #1e293b;\n  border: 2px solid #2563eb;\n  font-size: 10px;\n  font-weight: 700;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  cursor: pointer;\n  transition: transform 0.15s, z-index 0.15s;\n  position: relative;\n  margin-left: -5px;\n  color: #f8fafc;\n}\n\n.collab-avatar-chip:first-child {\n  margin-left: 0;\n}\n\n.collab-avatar-chip:hover {\n  transform: translateY(-2px) scale(1.15);\n  z-index: 10;\n}\n\n.collab-self-tag {\n  font-size: 11px;\n  font-weight: 500;\n  color: #94a3b8;\n  letter-spacing: 0.2px;\n}\n\n/* Remote Cursors on Canvas */\n.remote-canvas-cursor {\n  position: absolute;\n  pointer-events: none;\n  z-index: 999;\n  transform: translate(0, 0);\n  transition: left 0.08s ease-out, top 0.08s ease-out;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.remote-cursor-arrow {\n  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.25));\n}\n\n.remote-cursor-label {\n  display: inline-block;\n  padding: 2px 6px;\n  border-radius: 4px;\n  color: #ffffff;\n  font-size: 10px;\n  font-weight: 600;\n  white-space: nowrap;\n  box-shadow: 0 2px 5px rgba(0,0,0,0.2);\n  margin-left: 12px;\n  margin-top: -6px;\n}\n\n/* Remote Selected Node Highlight */\n.remote-selected-node {\n  box-shadow: 0 0 0 3px var(--remote-color, #2563eb), 0 0 14px var(--remote-color, #2563eb) !important;\n  transition: box-shadow 0.2s ease;\n}\n\n.remote-user-tag {\n  position: absolute;\n  top: -18px;\n  right: 0;\n  padding: 1px 6px;\n  border-radius: 3px;\n  color: #ffffff;\n  font-size: 9px;\n  font-weight: 700;\n  pointer-events: none;\n  white-space: nowrap;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n  z-index: 50;\n  animation: tag-pop 0.2s ease-out;\n}\n\n@keyframes tag-pop {\n  from { opacity: 0; transform: translateY(4px); }\n  to { opacity: 1; transform: translateY(0); }\n}\n\n/* Node card attachment paperclip badge */\n.node-card-att-badge {\n  display: inline-flex;\n  align-items: center;\n  background: rgba(15, 23, 42, 0.12);\n  color: inherit;\n  padding: 1px 5px;\n  border-radius: 10px;\n  font-size: 9px;\n  font-weight: 600;\n  margin-left: 4px;\n  cursor: pointer;\n  vertical-align: middle;\n}\n\n.node-card-att-badge:hover {\n  background: rgba(15, 23, 42, 0.25);\n}\n\n/* ==========================================================================\n   Element Document Attachments in Inspector\n   ========================================================================== */\n\n.element-attachments-box {\n  margin-top: 14px;\n  padding: 10px;\n  background: #f8fafc;\n  border: 1px solid var(--border-light, #e2e8f0);\n  border-radius: 6px;\n}\n\n.attachment-upload-zone {\n  margin-bottom: 8px;\n}\n\n.attachment-upload-zone.drag-over {\n  border: 2px dashed #2563eb;\n  background: #eff6ff;\n}\n\n.attachments-mini-list {\n  display: flex;\n  flex-direction: column;\n  gap: 6px;\n  max-height: 200px;\n  overflow-y: auto;\n}\n\n.attachment-item-card {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 6px 8px;\n  background: #ffffff;\n  border: 1px solid #e2e8f0;\n  border-radius: 4px;\n  font-size: 11px;\n  transition: border-color 0.15s;\n}\n\n.attachment-item-card:hover {\n  border-color: #cbd5e1;\n}\n\n.attachment-item-card.uploading {\n  opacity: 0.7;\n  font-style: italic;\n}\n\n.att-icon-wrap {\n  display: flex;\n  align-items: center;\n  margin-right: 6px;\n  flex-shrink: 0;\n}\n\n.att-info-wrap {\n  flex: 1;\n  min-width: 0;\n  margin-right: 6px;\n}\n\n.att-filename {\n  font-weight: 600;\n  color: #1e293b;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.att-meta-line {\n  font-size: 10px;\n  color: #64748b;\n  display: flex;\n  gap: 4px;\n}\n\n.att-actions-wrap {\n  display: flex;\n  align-items: center;\n  gap: 4px;\n  flex-shrink: 0;\n}\n\n.btn-att-download,\n.btn-att-delete {\n  padding: 3px 5px !important;\n  font-size: 10px !important;\n  line-height: 1 !important;\n}\n\n/* ==========================================================================\n   ACTIVE SITUATION RUNNER & INCIDENT SIMULATOR STYLES\n   ========================================================================== */\n\n/* Situation Runner HUD Toolbar Toggle Button */\n.btn-tool-sim {\n  position: relative;\n  background: #1e293b;\n  color: #f8fafc;\n  border-color: #334155;\n  font-weight: 600;\n}\n\n.btn-tool-sim:hover {\n  background: #0f172a;\n  border-color: #f59e0b;\n  color: #fbbf24;\n}\n\n.btn-tool-sim.active {\n  background: #d97706;\n  border-color: #f59e0b;\n  color: #ffffff;\n  box-shadow: 0 0 12px rgba(245, 158, 11, 0.4);\n}\n\n.sim-status-dot {\n  width: 7px;\n  height: 7px;\n  border-radius: 50%;\n  background: #94a3b8;\n  display: inline-block;\n  margin-right: 2px;\n}\n\n.btn-tool-sim.active .sim-status-dot,\n.sim-status-dot.running {\n  background: #10b981;\n  box-shadow: 0 0 8px #10b981;\n  animation: pulse-dot 1.2s infinite;\n}\n\n/* Inline Card Quick-Run Button */\n.btn-node-sim {\n  color: #fbbf24;\n  font-size: 11px;\n}\n.btn-node-sim:hover {\n  background: rgba(251, 191, 36, 0.2);\n  color: #f59e0b;\n}\n\n/* Floating Situation Runner HUD */\n.situation-runner-hud {\n  position: absolute;\n  top: 55px;\n  right: 20px;\n  width: 420px;\n  max-width: calc(100vw - 40px);\n  background: rgba(15, 23, 42, 0.95);\n  backdrop-filter: blur(14px);\n  -webkit-backdrop-filter: blur(14px);\n  border: 1px solid rgba(255, 255, 255, 0.12);\n  border-radius: 12px;\n  box-shadow: 0 16px 36px -4px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05);\n  color: #f8fafc;\n  z-index: 980 !important;\n  transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n  overflow: hidden;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n  touch-action: manipulation;\n  -webkit-user-select: auto;\n  user-select: auto;\n  pointer-events: auto;\n  -webkit-tap-highlight-color: rgba(255, 255, 255, 0.15);\n}\n\n.situation-runner-hud * {\n  touch-action: manipulation;\n  pointer-events: auto;\n}\n\n/* Responsive button text spans */\n.btn-text-full {\n  display: inline;\n}\n.btn-text-short {\n  display: none;\n}\n\n.situation-runner-hud.collapsed {\n  width: 320px;\n}\n\n.situation-runner-hud.collapsed .sim-hud-body {\n  display: none;\n}\n\n.situation-runner-hud.closed {\n  display: none !important;\n  opacity: 0 !important;\n  visibility: hidden !important;\n  pointer-events: none !important;\n}\n\n.situation-runner-hud.is-dragging {\n  transition: none !important;\n  user-select: none !important;\n  -webkit-user-select: none !important;\n  box-shadow: 0 22px 48px -4px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(251, 191, 36, 0.35);\n  cursor: grabbing !important;\n}\n\n.situation-runner-hud.is-moved {\n  right: auto !important;\n}\n\n.sim-hud-header {\n  padding: 10px 14px;\n  background: rgba(30, 41, 59, 0.85);\n  border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  cursor: grab;\n  user-select: none;\n  touch-action: none;\n}\n\n.sim-hud-header:active,\n.situation-runner-hud.is-dragging .sim-hud-header {\n  cursor: grabbing;\n}\n\n.sim-drag-grip {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  color: #64748b;\n  margin-right: 2px;\n  flex-shrink: 0;\n  opacity: 0.7;\n  transition: opacity 0.15s, color 0.15s;\n}\n\n.sim-hud-header:hover .sim-drag-grip {\n  opacity: 1;\n  color: #94a3b8;\n}\n\n.situation-runner-hud.is-dragging .sim-drag-grip {\n  color: #fbbf24;\n  opacity: 1;\n}\n\n.sim-hud-header-actions button {\n  cursor: pointer;\n  touch-action: manipulation;\n}\n\n.sim-hud-title {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  flex: 1;\n  min-width: 0;\n}\n\n.sim-radar-icon {\n  font-size: 14px;\n  color: #fbbf24;\n  animation: pulse-dot 1.5s infinite;\n}\n\n.sim-hud-heading {\n  font-size: 11px;\n  font-weight: 700;\n  letter-spacing: 0.08em;\n  color: #e2e8f0;\n  white-space: nowrap;\n}\n\n.sim-mode-tag {\n  font-size: 10px;\n  font-weight: 600;\n  padding: 2px 6px;\n  border-radius: 4px;\n  background: rgba(255, 255, 255, 0.1);\n  color: #94a3b8;\n  white-space: nowrap;\n}\n\n.sim-status-pill {\n  font-size: 9px;\n  font-weight: 700;\n  padding: 2px 7px;\n  border-radius: 9999px;\n  letter-spacing: 0.05em;\n  text-transform: uppercase;\n  flex-shrink: 0;\n  margin-right: 6px;\n}\n\n.sim-status-pill.idle {\n  background: #334155;\n  color: #cbd5e1;\n}\n\n.sim-status-pill.running {\n  background: #15803d;\n  color: #dcfce7;\n  animation: pulse-dot 1.2s infinite;\n}\n\n.sim-status-pill.paused {\n  background: #d97706;\n  color: #fef3c7;\n}\n\n.sim-status-pill.breached {\n  background: #b91c1c;\n  color: #fee2e2;\n}\n\n.sim-status-pill.contained {\n  background: #047857;\n  color: #d1fae5;\n}\n\n.sim-hud-header-actions {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  margin-left: 12px;\n  flex-shrink: 0;\n}\n\n.btn-sim-icon {\n  background: none;\n  border: none;\n  color: #94a3b8;\n  width: 28px;\n  height: 28px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 6px;\n  cursor: pointer;\n  font-size: 18px;\n  touch-action: manipulation;\n  -webkit-tap-highlight-color: transparent;\n  flex-shrink: 0;\n}\n\n.btn-sim-icon:hover {\n  background: rgba(255, 255, 255, 0.1);\n  color: #ffffff;\n}\n\n.sim-hud-body {\n  padding: 12px 14px;\n  display: flex;\n  flex-direction: column;\n  gap: 10px;\n}\n\n.sim-row {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.sim-label {\n  font-size: 11px;\n  font-weight: 600;\n  color: #94a3b8;\n  white-space: nowrap;\n}\n\n.sim-sublabel {\n  font-size: 10px;\n  font-weight: 600;\n  color: #64748b;\n  margin-right: 4px;\n  white-space: nowrap;\n}\n\n.sim-select {\n  flex: 1;\n  background: #1e293b;\n  border: 1px solid #334155;\n  border-radius: 6px;\n  color: #f1f5f9;\n  font-size: 11px;\n  padding: 5px 8px;\n  outline: none;\n}\n\n.sim-select:focus {\n  border-color: #3b82f6;\n}\n\n/* Mode Pills */\n.sim-pill-group {\n  display: flex;\n  gap: 4px;\n  background: #1e293b;\n  padding: 3px;\n  border-radius: 6px;\n  border: 1px solid #334155;\n}\n\n.sim-pill {\n  flex: 1;\n  background: transparent;\n  border: none;\n  color: #94a3b8;\n  padding: 4px 8px;\n  border-radius: 4px;\n  font-size: 11px;\n  font-weight: 500;\n  cursor: pointer;\n  transition: all 0.15s;\n  white-space: nowrap;\n  text-align: center;\n}\n\n.sim-pill:hover {\n  color: #f1f5f9;\n}\n\n.sim-pill.active {\n  background: #2563eb;\n  color: #ffffff;\n  font-weight: 600;\n}\n\n/* Speed Multipliers */\n.sim-speed-group {\n  display: flex;\n  align-items: center;\n  gap: 3px;\n  margin-left: auto;\n}\n\n.sim-speed-btn {\n  background: #1e293b;\n  border: 1px solid #334155;\n  color: #94a3b8;\n  padding: 2px 6px;\n  border-radius: 4px;\n  font-size: 10px;\n  font-weight: 600;\n  cursor: pointer;\n}\n\n.sim-speed-btn:hover {\n  background: #334155;\n  color: #f8fafc;\n}\n\n.sim-speed-btn.active {\n  background: #f59e0b;\n  border-color: #f59e0b;\n  color: #0f172a;\n  font-weight: 700;\n}\n\n/* Schedule Buttons */\n.sim-sched-btn {\n  background: #1e293b;\n  border: 1px solid #334155;\n  color: #94a3b8;\n  padding: 3px 8px;\n  border-radius: 4px;\n  font-size: 10px;\n  cursor: pointer;\n}\n\n.sim-sched-btn:hover {\n  color: #ffffff;\n}\n\n.sim-sched-btn.active {\n  background: #3b82f6;\n  border-color: #3b82f6;\n  color: #ffffff;\n}\n\n/* Clock Card */\n.sim-clock-card {\n  background: rgba(2, 6, 23, 0.7);\n  border: 1px solid rgba(255, 255, 255, 0.08);\n  border-radius: 8px;\n  padding: 8px 12px;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n}\n\n.sim-clock-display {\n  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n  font-size: 24px;\n  font-weight: 700;\n  color: #38bdf8;\n  letter-spacing: 0.05em;\n  text-shadow: 0 0 12px rgba(56, 189, 248, 0.4);\n}\n\n.sim-clock-details {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n}\n\n.sim-clock-metric {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  font-size: 10px;\n}\n\n.metric-label {\n  color: #64748b;\n}\n\n.metric-val {\n  color: #cbd5e1;\n  font-weight: 500;\n  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n}\n\n.metric-val.text-accent {\n  color: #fbbf24;\n  font-weight: 600;\n  font-family: inherit;\n}\n\n/* Progress Track */\n.sim-progress-track {\n  width: 100%;\n  height: 5px;\n  background: #1e293b;\n  border-radius: 9999px;\n  overflow: hidden;\n  position: relative;\n}\n\n.sim-progress-fill {\n  height: 100%;\n  background: linear-gradient(90deg, #3b82f6, #f59e0b, #ef4444);\n  transition: width 0.15s ease-out;\n  border-radius: 9999px;\n}\n\n/* Transport Buttons */\n.sim-transport-row {\n  display: flex;\n  gap: 6px;\n  align-items: center;\n}\n\n.sim-transport-btn {\n  flex: 1;\n  background: #1e293b;\n  border: 1px solid #334155;\n  color: #f1f5f9;\n  border-radius: 6px;\n  padding: 6px 10px;\n  font-size: 11px;\n  font-weight: 600;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 5px;\n  cursor: pointer;\n  transition: all 0.15s;\n}\n\n.sim-transport-btn:hover:not(:disabled) {\n  background: #334155;\n  border-color: #475569;\n}\n\n.sim-transport-btn:disabled {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\n.btn-sim-play {\n  background: #16a34a;\n  border-color: #22c55e;\n  color: #ffffff;\n  flex: 1.5;\n}\n\n.btn-sim-play:hover:not(:disabled) {\n  background: #15803d;\n  border-color: #16a34a;\n}\n\n.btn-sim-pause {\n  background: #d97706;\n  border-color: #f59e0b;\n  color: #ffffff;\n}\n\n.btn-sim-pause:hover:not(:disabled) {\n  background: #b45309;\n}\n\n.btn-sim-reset {\n  background: #334155;\n}\n\n.btn-sim-log-toggle {\n  flex: 1;\n  background: #1e293b;\n}\n\n/* Incident Log Drawer */\n.sim-log-drawer {\n  background: rgba(2, 6, 23, 0.95);\n  border: 1px solid rgba(255, 255, 255, 0.08);\n  border-radius: 8px;\n  padding: 8px;\n  margin-top: 4px;\n  max-height: 200px;\n  display: flex;\n  flex-direction: column;\n}\n\n.sim-log-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  font-size: 10px;\n  font-weight: 600;\n  color: #94a3b8;\n  padding-bottom: 6px;\n  border-bottom: 1px solid rgba(255, 255, 255, 0.06);\n  margin-bottom: 6px;\n}\n\n.sim-log-actions {\n  display: flex;\n  gap: 4px;\n}\n\n.sim-log-list {\n  flex: 1;\n  overflow-y: auto;\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  font-size: 11px;\n  max-height: 150px;\n}\n\n.sim-log-empty {\n  color: #64748b;\n  font-size: 11px;\n  text-align: center;\n  padding: 16px 8px;\n}\n\n.sim-log-item {\n  display: flex;\n  align-items: flex-start;\n  gap: 6px;\n  padding: 3px 6px;\n  border-radius: 4px;\n  background: rgba(255, 255, 255, 0.03);\n  line-height: 1.3;\n}\n\n.sim-log-time {\n  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n  font-size: 10px;\n  color: #38bdf8;\n  white-space: nowrap;\n}\n\n.sim-log-msg {\n  color: #e2e8f0;\n  word-break: break-word;\n}\n\n.sim-log-item.log-threat .sim-log-msg { color: #f87171; }\n.sim-log-item.log-impact .sim-log-msg { color: #fbbf24; font-weight: 600; }\n.sim-log-item.log-holding .sim-log-msg { color: #fde047; }\n.sim-log-item.log-breached .sim-log-msg { color: #ef4444; font-weight: 700; }\n.sim-log-item.log-contained .sim-log-msg { color: #4ade80; font-weight: 700; }\n.sim-log-item.log-top-event .sim-log-msg { color: #f43f5e; font-weight: 700; }\n\n/* Dynamic Card Simulation Badges */\n.sim-card-badge {\n  position: absolute;\n  bottom: -12px;\n  right: 8px;\n  padding: 3px 8px;\n  border-radius: 9999px;\n  font-size: 9.5px;\n  font-weight: 700;\n  letter-spacing: 0.04em;\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.25);\n  z-index: 50 !important;\n  pointer-events: none;\n  text-transform: uppercase;\n  white-space: nowrap;\n}\n\n.badge-threat-active {\n  background: #2563eb;\n  color: #ffffff;\n  box-shadow: 0 0 10px rgba(37, 99, 235, 0.8);\n  animation: pulse-dot 1s infinite;\n}\n\n.badge-inbound {\n  background: #3b82f6;\n  color: #ffffff;\n}\n\n.badge-arming, .badge-escalating {\n  background: #d97706;\n  color: #ffffff;\n  box-shadow: 0 0 10px rgba(217, 119, 6, 0.8);\n  animation: pulse-dot 1s infinite;\n}\n\n.badge-holding {\n  background: #eab308;\n  color: #0f172a;\n  animation: pulse-dot 1.2s infinite;\n}\n\n.badge-outcome-holding {\n  background: #dc2626;\n  color: #ffffff;\n  box-shadow: 0 0 10px rgba(220, 38, 38, 0.8);\n  animation: pulse-dot 1.2s infinite;\n}\n\n.badge-mitigation-active {\n  background: #0284c7;\n  color: #ffffff;\n  box-shadow: 0 0 10px rgba(2, 132, 199, 0.8);\n  animation: pulse-dot 1.2s infinite;\n}\n\n.badge-breached {\n  background: #dc2626;\n  color: #ffffff;\n  box-shadow: 0 0 10px rgba(220, 38, 38, 0.8);\n}\n\n.badge-contained {\n  background: #16a34a;\n  color: #ffffff;\n  box-shadow: 0 0 10px rgba(22, 163, 74, 0.8);\n}\n\n.badge-topevent-breached {\n  background: #991b1b;\n  color: #fee2e2;\n  box-shadow: 0 0 14px rgba(153, 27, 27, 1);\n  animation: pulse-dot 0.8s infinite;\n}\n\n/* Visual Node Card States during simulation */\n.is-sim-active-threat {\n  border-color: #2563eb !important;\n  animation: threat-aura-pulse 1.6s infinite !important;\n  z-index: 35 !important;\n  overflow: visible !important;\n}\n\n.is-sim-challenged {\n  border-color: #f59e0b !important;\n  animation: barrier-holding-pulse 1.4s infinite !important;\n  z-index: 35 !important;\n  overflow: visible !important;\n}\n\n.is-sim-impacted {\n  animation: barrier-impact-shake 0.45s ease-out !important;\n  z-index: 35 !important;\n  overflow: visible !important;\n}\n\n.is-sim-holding {\n  border-color: #f59e0b !important;\n  animation: barrier-holding-pulse 1.4s infinite !important;\n  z-index: 35 !important;\n  overflow: visible !important;\n}\n\n/* Outcome Barriers: RED consequence containment pulse */\n.is-sim-outcome-holding,\n.is-sim-outcome-barrier-active {\n  border-color: #ef4444 !important;\n  animation: outcome-barrier-pulse 1.3s infinite !important;\n  z-index: 35 !important;\n  overflow: visible !important;\n}\n\n/* Escalation Factors: YELLOW/AMBER pulse */\n.is-sim-escalation-pulsing {\n  border-color: #eab308 !important;\n  animation: escalation-factor-pulse 1.2s infinite !important;\n  z-index: 35 !important;\n  overflow: visible !important;\n}\n\n/* Mitigations: BLUE/CYAN pulse */\n.is-sim-mitigation-active {\n  border-color: #0284c7 !important;\n  animation: mitigation-active-pulse 1.3s infinite !important;\n  z-index: 35 !important;\n  overflow: visible !important;\n}\n\n.is-sim-breached {\n  border-color: #ef4444 !important;\n  animation: barrier-breached-pulse 1.2s infinite !important;\n  opacity: 0.95;\n  z-index: 35 !important;\n  overflow: visible !important;\n}\n\n.is-sim-contained {\n  border-color: #10b981 !important;\n  animation: barrier-contained-pulse 1.5s infinite !important;\n  z-index: 35 !important;\n  overflow: visible !important;\n}\n\n.is-sim-topevent-alarm {\n  border-color: #dc2626 !important;\n  animation: top-event-alarm 1s infinite !important;\n  z-index: 35 !important;\n  overflow: visible !important;\n}\n\n/* Animations Keyframes */\n@keyframes threat-aura-pulse {\n  0%, 100% {\n    box-shadow: 0 0 0 2px #2563eb, 0 0 16px rgba(37, 99, 235, 0.7);\n  }\n  50% {\n    box-shadow: 0 0 0 6px #3b82f6, 0 0 28px rgba(59, 130, 246, 0.95);\n  }\n}\n\n@keyframes barrier-impact-shake {\n  0% { transform: scale(1); filter: brightness(1); }\n  25% { transform: scale(1.08); filter: brightness(1.4); }\n  50% { transform: scale(0.96); filter: brightness(1.2); }\n  75% { transform: scale(1.03); filter: brightness(1.1); }\n  100% { transform: scale(1); filter: brightness(1); }\n}\n\n@keyframes barrier-holding-pulse {\n  0%, 100% {\n    box-shadow: 0 0 0 2px #f59e0b, 0 0 14px rgba(245, 158, 11, 0.5);\n  }\n  50% {\n    box-shadow: 0 0 0 4px #fbbf24, 0 0 22px rgba(245, 158, 11, 0.75);\n  }\n}\n\n@keyframes outcome-barrier-pulse {\n  0%, 100% {\n    box-shadow: 0 0 0 2px #ef4444, 0 0 16px rgba(239, 68, 68, 0.65);\n  }\n  50% {\n    box-shadow: 0 0 0 5px #dc2626, 0 0 26px rgba(220, 38, 38, 0.9);\n  }\n}\n\n@keyframes escalation-factor-pulse {\n  0%, 100% {\n    box-shadow: 0 0 0 2px #eab308, 0 0 14px rgba(234, 179, 8, 0.6);\n    transform: scale(1);\n  }\n  50% {\n    box-shadow: 0 0 0 4px #f59e0b, 0 0 24px rgba(245, 158, 11, 0.85);\n    transform: scale(1.02);\n  }\n}\n\n@keyframes mitigation-active-pulse {\n  0%, 100% {\n    box-shadow: 0 0 0 2px #0284c7, 0 0 14px rgba(2, 132, 199, 0.6);\n  }\n  50% {\n    box-shadow: 0 0 0 4px #38bdf8, 0 0 22px rgba(56, 189, 248, 0.85);\n  }\n}\n\n@keyframes barrier-breached-pulse {\n  0%, 100% {\n    box-shadow: 0 0 0 2px #ef4444, 0 0 14px rgba(239, 68, 68, 0.6);\n  }\n  50% {\n    box-shadow: 0 0 0 5px #dc2626, 0 0 24px rgba(239, 68, 68, 0.85);\n  }\n}\n\n@keyframes barrier-contained-pulse {\n  0%, 100% {\n    box-shadow: 0 0 0 2px #10b981, 0 0 14px rgba(16, 185, 129, 0.6);\n  }\n  50% {\n    box-shadow: 0 0 0 5px #059669, 0 0 24px rgba(16, 185, 129, 0.85);\n  }\n}\n\n@keyframes top-event-alarm {\n  0%, 100% {\n    box-shadow: 0 0 0 4px #dc2626, 0 0 30px rgba(220, 38, 38, 0.8);\n  }\n  50% {\n    box-shadow: 0 0 0 8px #ef4444, 0 0 50px rgba(239, 68, 68, 1);\n  }\n}\n\n/* SVG Threat Traveling Ball & Shockwave */\n.threat-ball-group {\n  pointer-events: none;\n}\n\n.threat-ball-core {\n  fill: #ffffff;\n  stroke: #ef4444;\n  stroke-width: 2.5;\n}\n\n.threat-ball-mid {\n  fill: rgba(239, 68, 68, 0.65);\n}\n\n.threat-ball-aura {\n  fill: rgba(239, 68, 68, 0.35);\n  animation: threat-ball-travel-pulse 0.8s infinite alternate;\n}\n\n@keyframes threat-ball-travel-pulse {\n  0% { r: 12px; fill-opacity: 0.25; }\n  100% { r: 18px; fill-opacity: 0.55; }\n}\n\n.sim-shockwave-pulse {\n  fill: none;\n  stroke: #f59e0b;\n  stroke-width: 3;\n  animation: sim-expand-shockwave 0.65s cubic-bezier(0.1, 0.8, 0.3, 1) forwards;\n}\n\n@keyframes sim-expand-shockwave {\n  0% { r: 6px; stroke: #f59e0b; stroke-opacity: 1; stroke-width: 4px; }\n  100% { r: 60px; stroke: #ef4444; stroke-opacity: 0; stroke-width: 1px; }\n}\n\n/* Inspector Simulation Timing Box */\n.simulation-timing-box {\n  background: #f8fafc;\n  border: 1px solid #e2e8f0;\n  border-radius: 6px;\n  padding: 10px;\n  margin-bottom: 12px;\n}\n\n.status-pill-sim {\n  background: #dbeafe;\n  color: #1e40af;\n  font-weight: 600;\n  font-size: 10px;\n  padding: 2px 6px;\n  border-radius: 9999px;\n}\n\n/* Touch active feedback */\n.sim-transport-btn:active:not(:disabled),\n.sim-pill:active,\n.sim-speed-btn:active,\n.sim-sched-btn:active,\n.btn-sim-icon:active {\n  transform: scale(0.96);\n  filter: brightness(1.2);\n}\n\n/* Mobile Responsiveness for Situation Runner HUD */\n@media (max-width: 600px) {\n  .situation-runner-hud {\n    top: 54px;\n    left: 8px;\n    right: 8px;\n    width: auto;\n    max-width: calc(100vw - 16px);\n    border-radius: 10px;\n    z-index: 980 !important;\n  }\n\n  .situation-runner-hud.collapsed {\n    left: 8px;\n    right: 8px;\n    width: auto;\n  }\n\n  .situation-runner-hud.is-moved {\n    right: auto !important;\n    width: min(420px, calc(100vw - 16px)) !important;\n  }\n\n  .situation-runner-hud.is-moved.collapsed {\n    width: min(320px, calc(100vw - 16px)) !important;\n  }\n\n  .sim-hud-header {\n    padding: 8px 12px;\n    gap: 10px;\n  }\n\n  .sim-mode-tag {\n    display: none;\n  }\n\n  .sim-hud-header-actions {\n    margin-left: 12px;\n  }\n\n  .sim-hud-body {\n    padding: 8px 10px;\n    gap: 8px;\n  }\n\n  /* Wrap mode pills and speed onto two clean lines on small phones */\n  .sim-row-mode {\n    flex-direction: column;\n    align-items: stretch;\n    gap: 6px;\n  }\n\n  .sim-speed-group {\n    margin-left: 0;\n    justify-content: flex-end;\n  }\n\n  /* Use compact button text on small screens */\n  .btn-text-full {\n    display: none;\n  }\n  .btn-text-short {\n    display: inline;\n  }\n\n  .sim-transport-row {\n    display: flex;\n    gap: 4px;\n  }\n\n  .sim-transport-btn {\n    padding: 7px 4px;\n    font-size: 10px;\n    gap: 2px;\n  }\n\n  .btn-sim-play {\n    flex: 1.1;\n  }\n\n  .sim-clock-card {\n    padding: 6px 10px;\n    gap: 8px;\n  }\n\n  .sim-clock-display {\n    font-size: 20px;\n  }\n\n  .btn-sim-icon {\n    width: 32px;\n    height: 32px;\n    font-size: 18px;\n  }\n}\n\n/* ==========================================================================\n   SITUATION RUNNER ELAPSED TIME & PAST ACTIVATION\n   ========================================================================== */\n.sim-schedule-container {\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n  width: 100%;\n}\n\n.sim-schedule-nav {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  flex-wrap: wrap;\n}\n\n.sim-past-elapsed-box {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  padding: 8px 10px;\n  background: rgba(30, 41, 59, 0.9);\n  border: 1px solid rgba(59, 130, 246, 0.3);\n  border-radius: 6px;\n  flex-wrap: wrap;\n}\n\n.sim-sublabel-accent {\n  font-size: 11px;\n  font-weight: 600;\n  color: #60a5fa;\n  white-space: nowrap;\n}\n\n.sim-time-field {\n  display: flex;\n  align-items: center;\n  gap: 4px;\n}\n\n.sim-time-input {\n  width: 54px;\n  background: #0f172a;\n  border: 1px solid #334155;\n  color: #f8fafc;\n  border-radius: 4px;\n  padding: 3px 6px;\n  font-size: 12px;\n  font-weight: 600;\n  text-align: center;\n}\n\n.sim-time-input:focus {\n  outline: none;\n  border-color: #3b82f6;\n  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);\n}\n\n.sim-unit {\n  font-size: 11px;\n  color: #94a3b8;\n  white-space: nowrap;\n}\n\n.sim-quick-elapsed-pills {\n  display: flex;\n  align-items: center;\n  gap: 4px;\n  margin-left: auto;\n}\n\n.sim-quick-pill {\n  background: #1e293b;\n  border: 1px solid #475569;\n  color: #93c5fd;\n  border-radius: 4px;\n  padding: 2px 6px;\n  font-size: 10px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: all 0.15s ease;\n}\n\n.sim-quick-pill:hover {\n  background: #2563eb;\n  color: #ffffff;\n  border-color: #2563eb;\n}\n\n/* ==========================================================================\n   FINANCIAL COSTING & PROTECTION ANALYSIS\n   ========================================================================== */\n.financial-costing-box {\n  margin-top: 10px;\n  margin-bottom: 12px;\n  padding: 10px 12px;\n  border: 1px solid var(--border-light);\n  border-radius: 8px;\n  background: #f8fafc;\n}\n\n.status-pill-cost {\n  background: #ecfdf5;\n  color: #047857;\n  border: 1px solid #a7f3d0;\n  font-size: 10px;\n  font-weight: 600;\n  padding: 2px 6px;\n  border-radius: 10px;\n}\n\n.cost-protection-metric-card {\n  margin-top: 8px;\n  padding: 8px 10px;\n  background: #ffffff;\n  border: 1px solid #e2e8f0;\n  border-radius: 6px;\n}\n\n.cost-protection-metric-card .metric-row {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  font-size: 11.5px;\n  margin-bottom: 4px;\n}\n\n/* Modal: Cost vs Protection Matrix */\n.cost-summary-cards-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n  gap: 12px;\n  margin-bottom: 16px;\n}\n\n.cost-stat-card {\n  background: #f8fafc;\n  border: 1px solid #e2e8f0;\n  border-radius: 8px;\n  padding: 12px;\n  display: flex;\n  flex-direction: column;\n}\n\n.cost-stat-label {\n  font-size: 11px;\n  font-weight: 600;\n  text-transform: uppercase;\n  letter-spacing: 0.04em;\n  color: #64748b;\n  margin-bottom: 4px;\n}\n\n.cost-stat-val {\n  font-size: 22px;\n  font-weight: 700;\n  line-height: 1.2;\n}\n\n.cost-stat-sub {\n  font-size: 11px;\n  color: #94a3b8;\n  margin-top: 4px;\n}\n\n.cost-analysis-table {\n  width: 100%;\n  border-collapse: collapse;\n  font-size: 12.5px;\n}\n\n.cost-analysis-table th {\n  text-align: left;\n  padding: 8px 10px;\n  background: #f1f5f9;\n  border-bottom: 1px solid #cbd5e1;\n  font-weight: 600;\n  color: #334155;\n  font-size: 11.5px;\n  text-transform: uppercase;\n}\n\n.cost-analysis-table td {\n  padding: 9px 10px;\n  border-bottom: 1px solid #e2e8f0;\n  vertical-align: middle;\n}\n\n.cost-analysis-table tr:hover td {\n  background: #f8fafc;\n}\n\n/* Toolbar Buttons: Undo / Redo / Cost */\n.btn-tool-undo,\n.btn-tool-redo {\n  font-size: 11.5px;\n  gap: 5px;\n}\n\n.btn-tool-undo:disabled,\n.btn-tool-redo:disabled {\n  opacity: 0.4;\n  cursor: not-allowed;\n  filter: grayscale(1);\n}\n\n.btn-tool-cost {\n  color: #059669;\n  border-color: rgba(5, 150, 105, 0.3);\n}\n\n.btn-tool-cost:hover {\n  background: #ecfdf5;\n  color: #047857;\n  border-color: #059669;\n}\n\n/* History Toolbar Button and Dropdown */\n.btn-tool-history {\n  font-size: 11.5px;\n  gap: 5px;\n  position: relative;\n}\n\n.history-count-badge {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 16px;\n  height: 16px;\n  padding: 0 4px;\n  border-radius: 9999px;\n  background: #3b82f6;\n  color: #ffffff;\n  font-size: 10px;\n  font-weight: 700;\n  line-height: 1;\n}\n\n.diagram-history-backdrop {\n  position: fixed;\n  inset: 0;\n  background: rgba(15, 23, 42, 0.45);\n  backdrop-filter: blur(2px);\n  -webkit-backdrop-filter: blur(2px);\n  z-index: 10040;\n}\n@media (min-width: 901px) {\n  .diagram-history-backdrop {\n    display: none !important;\n  }\n}\n\n.diagram-history-dropdown {\n  position: absolute;\n  top: calc(100% + 6px);\n  left: 0;\n  width: 320px;\n  max-height: 420px;\n  background: #ffffff;\n  border: 1px solid #e2e8f0;\n  border-radius: 10px;\n  box-shadow: 0 12px 30px -4px rgba(0, 0, 0, 0.16), 0 4px 10px -2px rgba(0, 0, 0, 0.08);\n  z-index: 10050;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  animation: fadeIn 0.15s cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.diagram-history-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 10px 14px;\n  border-bottom: 1px solid #f1f5f9;\n  background: #f8fafc;\n}\n\n.history-header-title {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  font-size: 12px;\n  font-weight: 700;\n  color: #1e293b;\n  text-transform: uppercase;\n  letter-spacing: 0.5px;\n}\n\n.history-header-title svg {\n  color: #3b82f6;\n}\n\n.diagram-history-list {\n  flex: 1;\n  overflow-y: auto;\n  padding: 6px 0;\n  max-height: 360px;\n}\n\n.history-entry-item {\n  display: flex;\n  align-items: flex-start;\n  gap: 10px;\n  padding: 8px 14px;\n  border: none;\n  background: transparent;\n  width: 100%;\n  text-align: left;\n  cursor: pointer;\n  transition: background 0.15s ease, border-color 0.15s ease;\n  border-left: 3px solid transparent;\n}\n\n.history-entry-item:hover {\n  background: #f1f5f9;\n}\n\n.history-entry-item.is-current {\n  background: #eff6ff;\n  border-left-color: #3b82f6;\n  cursor: default;\n}\n\n.history-entry-item.is-redo {\n  opacity: 0.75;\n}\n\n.history-entry-item.is-redo:hover {\n  opacity: 1;\n  background: #f8fafc;\n}\n\n.history-entry-icon {\n  margin-top: 2px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 18px;\n  height: 18px;\n  border-radius: 50%;\n  flex-shrink: 0;\n  background: #f1f5f9;\n  color: #64748b;\n}\n\n.history-entry-item.is-current .history-entry-icon {\n  background: #3b82f6;\n  color: #ffffff;\n}\n\n.history-entry-content {\n  flex: 1;\n  min-width: 0;\n}\n\n.history-entry-title {\n  font-size: 12.5px;\n  font-weight: 600;\n  color: #1e293b;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  line-height: 1.3;\n}\n\n.history-entry-sub {\n  font-size: 11px;\n  color: #64748b;\n  margin-top: 2px;\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n\n.history-entry-time {\n  color: #94a3b8;\n  font-size: 10.5px;\n  margin-left: auto;\n  white-space: nowrap;\n}\n\n.history-empty-state {\n  padding: 24px 16px;\n  text-align: center;\n  color: #64748b;\n  font-size: 12px;\n}\n\n.history-empty-icon {\n  margin-bottom: 8px;\n  color: #94a3b8;\n}\n\n/* ==========================================================================\n   Auth Wall Screen (Google Docs Style Secure Login Portal)\n   ========================================================================== */\n.auth-wall-screen {\n  position: fixed;\n  inset: 0;\n  width: 100vw;\n  height: 100vh;\n  height: 100dvh;\n  z-index: 99999;\n  background: radial-gradient(circle at 50% 20%, #1e293b 0%, #0f172a 60%, #020617 100%);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 20px;\n  overflow-y: auto;\n}\n\n.auth-wall-card {\n  width: 100%;\n  max-width: 440px;\n  background: rgba(15, 23, 42, 0.88);\n  backdrop-filter: blur(16px);\n  -webkit-backdrop-filter: blur(16px);\n  border: 1px solid rgba(255, 255, 255, 0.12);\n  border-radius: 16px;\n  padding: 36px 32px;\n  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7), 0 0 40px rgba(37, 99, 235, 0.15);\n  color: #f8fafc;\n  display: flex;\n  flex-direction: column;\n  animation: authWallFadeIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n@keyframes authWallFadeIn {\n  from { opacity: 0; transform: translateY(12px) scale(0.98); }\n  to { opacity: 1; transform: translateY(0) scale(1); }\n}\n\n.auth-wall-logo {\n  display: flex;\n  justify-content: center;\n  margin-bottom: 24px;\n}\n\n.auth-wall-header {\n  text-align: center;\n  margin-bottom: 24px;\n}\n\n.auth-wall-title {\n  font-size: 20px;\n  font-weight: 700;\n  letter-spacing: -0.02em;\n  color: #ffffff;\n  margin: 0 0 8px 0;\n}\n\n.auth-wall-desc {\n  font-size: 13px;\n  color: #94a3b8;\n  line-height: 1.45;\n  margin: 0;\n}\n\n.auth-wall-tabs {\n  display: flex;\n  background: rgba(30, 41, 59, 0.8);\n  border: 1px solid rgba(255, 255, 255, 0.08);\n  border-radius: 8px;\n  padding: 3px;\n  margin-bottom: 20px;\n  gap: 4px;\n}\n\n.auth-wall-tab-btn {\n  flex: 1;\n  padding: 8px 12px;\n  background: transparent;\n  border: none;\n  border-radius: 6px;\n  color: #94a3b8;\n  font-size: 13px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: all 0.15s ease;\n}\n\n.auth-wall-tab-btn.active {\n  background: #2563eb;\n  color: #ffffff;\n  box-shadow: 0 2px 8px rgba(37, 99, 235, 0.4);\n}\n\n.auth-wall-error {\n  background: rgba(239, 68, 68, 0.15);\n  border: 1px solid rgba(239, 68, 68, 0.4);\n  color: #fca5a5;\n  padding: 10px 14px;\n  border-radius: 8px;\n  font-size: 12.5px;\n  margin-bottom: 18px;\n  line-height: 1.4;\n}\n\n.auth-wall-success {\n  background: rgba(16, 185, 129, 0.15);\n  border: 1px solid rgba(16, 185, 129, 0.4);\n  color: #6ee7b7;\n  padding: 10px 14px;\n  border-radius: 8px;\n  font-size: 12.5px;\n  margin-bottom: 18px;\n  line-height: 1.4;\n}\n\n.auth-link {\n  color: #60a5fa;\n  text-decoration: underline;\n  cursor: pointer;\n  transition: color 0.15s;\n}\n\n.auth-link:hover {\n  color: #93c5fd;\n}\n\n.auth-wall-form .form-group {\n  margin-bottom: 16px;\n}\n\n.auth-wall-form label {\n  display: block;\n  font-size: 12px;\n  font-weight: 600;\n  text-transform: uppercase;\n  letter-spacing: 0.04em;\n  color: #cbd5e1;\n  margin-bottom: 6px;\n}\n\n.auth-wall-form .form-input {\n  width: 100%;\n  padding: 10px 14px;\n  background: rgba(30, 41, 59, 0.9);\n  border: 1px solid rgba(255, 255, 255, 0.15);\n  border-radius: 8px;\n  color: #ffffff;\n  font-size: 14px;\n  box-sizing: border-box;\n  transition: border-color 0.15s, box-shadow 0.15s;\n}\n\n.auth-wall-form .form-input:focus {\n  outline: none;\n  border-color: #3b82f6;\n  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);\n}\n\n.auth-wall-form .btn-primary {\n  width: 100%;\n  padding: 11px 16px;\n  font-size: 14px;\n  font-weight: 600;\n  border-radius: 8px;\n  background: #2563eb;\n  color: #ffffff;\n  border: none;\n  cursor: pointer;\n  margin-top: 8px;\n  transition: background 0.15s, transform 0.1s;\n}\n\n.auth-wall-form .btn-primary:hover {\n  background: #1d4ed8;\n}\n\n.auth-wall-form .btn-primary:active {\n  transform: scale(0.99);\n}\n\n/* ==========================================================================\n   Document Tabs Bar (Desktop Multi-Tab Document Switcher)\n   ========================================================================== */\n.diagram-tabs-bar {\n  display: flex;\n  align-items: center;\n  background: #0f172a;\n  border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n  padding: 0 12px;\n  height: 38px;\n  min-height: 38px;\n  gap: 6px;\n  overflow: hidden;\n  z-index: 40;\n}\n\n.tabs-scroll-container {\n  display: flex;\n  align-items: center;\n  gap: 4px;\n  overflow-x: auto;\n  scrollbar-width: none;\n  -ms-overflow-style: none;\n  flex: 1;\n  height: 100%;\n  flex-wrap: nowrap;\n}\n\n.tabs-scroll-container::-webkit-scrollbar {\n  display: none;\n}\n\n.diagram-tab {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n  padding: 0 12px;\n  height: 30px;\n  background: rgba(30, 41, 59, 0.7);\n  border: 1px solid rgba(255, 255, 255, 0.08);\n  border-bottom: none;\n  border-radius: 6px 6px 0 0;\n  color: #94a3b8;\n  font-size: 12.5px;\n  font-weight: 500;\n  cursor: pointer;\n  white-space: nowrap;\n  user-select: none;\n  transition: background 0.15s, color 0.15s, border-color 0.15s;\n  max-width: 220px;\n  flex-shrink: 0;\n}\n\n.diagram-tab:hover {\n  background: rgba(51, 65, 85, 0.8);\n  color: #f1f5f9;\n}\n\n.diagram-tab.active {\n  background: #1e293b;\n  color: #ffffff;\n  font-weight: 600;\n  border-color: rgba(255, 255, 255, 0.15) rgba(255, 255, 255, 0.15) transparent rgba(255, 255, 255, 0.15);\n  position: relative;\n}\n\n.diagram-tab.active::after {\n  content: '';\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  height: 2px;\n  background: #3b82f6;\n  border-radius: 2px 2px 0 0;\n}\n\n.diagram-tab .tab-icon {\n  flex-shrink: 0;\n  color: #3b82f6;\n}\n\n.diagram-tab .tab-title {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.diagram-tab .tab-close-btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  background: transparent;\n  border: none;\n  color: #64748b;\n  font-size: 14px;\n  line-height: 1;\n  cursor: pointer;\n  padding: 0;\n  margin-left: 2px;\n  transition: background 0.15s, color 0.15s;\n}\n\n.diagram-tab .tab-close-btn:hover {\n  background: rgba(239, 68, 68, 0.2);\n  color: #ef4444;\n}\n\n.diagram-tab.doc-tab {\n  padding: 0 10px;\n  min-width: 38px;\n  justify-content: center;\n}\n\n.diagram-tab.doc-tab .tab-icon {\n  color: #3b82f6;\n  width: 15px;\n  height: 15px;\n}\n\n.diagram-tab.doc-tab:hover .tab-icon {\n  color: #60a5fa;\n}\n\n.diagram-tab.doc-tab.active .tab-icon {\n  color: #60a5fa;\n}\n\n.tab-btn-add {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 24px;\n  height: 24px;\n  border-radius: 50%;\n  background: transparent;\n  border: 1px solid transparent;\n  color: #94a3b8;\n  cursor: pointer;\n  transition: all 0.15s ease;\n  flex-shrink: 0;\n  margin-left: 2px;\n}\n\n.tab-btn-add:hover {\n  background: rgba(255, 255, 255, 0.12);\n  color: #f1f5f9;\n}\n\n/* Mobile responsive tabs scaling */\n@media (max-width: 768px) {\n  .diagram-tabs-bar {\n    height: 36px;\n    min-height: 36px;\n    padding: 0 8px;\n  }\n  .tabs-scroll-container {\n    -webkit-overflow-scrolling: touch;\n    gap: 3px;\n  }\n  .diagram-tab {\n    height: 28px;\n    padding: 0 8px;\n    font-size: 11.5px;\n    max-width: 130px;\n    gap: 5px;\n  }\n  .diagram-tab.doc-tab {\n    padding: 0 8px;\n    min-width: 32px;\n  }\n  .tab-btn-add {\n    width: 22px;\n    height: 22px;\n  }\n}\n\n/* Top Navbar Diagram Navigation Controls Group */\n.diagram-nav-controls {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  flex-wrap: nowrap;\n}\n\n.diagram-nav-controls .btn-nav {\n  height: 32px;\n  min-width: 32px;\n  padding: 0 8px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 6px;\n  box-sizing: border-box;\n  flex-shrink: 0;\n}\n\n#btnOpenHistory {\n  position: relative;\n}\n\n#historyCountBadge {\n  font-size: 10px;\n  font-weight: 700;\n  line-height: 1;\n  padding: 2px 4px;\n  border-radius: 6px;\n  background-color: #2563eb;\n  color: #ffffff;\n  margin-left: 3px;\n}\n\n.nav-control-spacer {\n  width: 14px;\n  height: 1px;\n}\n\n/* Sidebar Navigation Toggles (Top bar) */\n.sidebar-nav-toggle-btn {\n  color: #94a3b8;\n  padding: 6px;\n  border-radius: 6px;\n  background: transparent;\n  border: none;\n  cursor: pointer;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  transition: all 0.15s ease;\n}\n.sidebar-nav-toggle-btn:hover,\n.sidebar-nav-toggle-btn.active {\n  color: #f8fafc;\n  background: rgba(255, 255, 255, 0.1);\n}\n\n/* Sidebar Edge Restore Tabs (Floating on workspace boundaries when collapsed) */\n.sidebar-edge-tab {\n  position: absolute;\n  top: calc(60px + env(safe-area-inset-top, 0px));\n  width: 22px;\n  height: 36px;\n  background: #ffffff;\n  border: 1px solid var(--border-light, #e2e8f0);\n  color: #475569;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  cursor: pointer;\n  z-index: 100;\n  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);\n  transition: all 0.15s ease;\n  padding: 0;\n}\n.sidebar-edge-tab:hover {\n  background: #2563eb;\n  color: #ffffff;\n  border-color: #2563eb;\n}\n.sidebar-edge-tab.edge-left {\n  left: 0;\n  border-left: none;\n  border-top-right-radius: 6px;\n  border-bottom-right-radius: 6px;\n}\n.sidebar-edge-tab.edge-right {\n  right: 0;\n  border-right: none;\n  border-top-left-radius: 6px;\n  border-bottom-left-radius: 6px;\n}\n\n/* User Profile Avatar-Only Button */\n.btn-profile-avatar-only {\n  padding: 2px;\n  background: transparent;\n  border: 1px solid rgba(255, 255, 255, 0.15);\n  border-radius: 50%;\n  cursor: pointer;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  transition: all 0.15s ease;\n}\n.btn-profile-avatar-only:hover {\n  background: rgba(255, 255, 255, 0.1);\n  border-color: rgba(255, 255, 255, 0.35);\n}\n.user-email-label {\n  display: none !important;\n}\n\n/* Mobile Diagram Menu Drawer Panel & Burger Button */\n.btn-mobile-burger {\n  display: none;\n  color: #f8fafc;\n  background: rgba(255, 255, 255, 0.08);\n  border: 1px solid rgba(255, 255, 255, 0.15);\n  border-radius: 6px;\n  padding: 5px 7px;\n  cursor: pointer;\n  align-items: center;\n  justify-content: center;\n  transition: all 0.15s ease;\n}\n.btn-mobile-burger:hover {\n  background: rgba(255, 255, 255, 0.18);\n  color: #ffffff;\n}\n\n.mobile-menu-drawer {\n  position: fixed;\n  inset: 0;\n  z-index: 2000;\n  display: flex;\n  justify-content: flex-end;\n}\n.mobile-menu-backdrop {\n  position: fixed;\n  inset: 0;\n  background: rgba(15, 23, 42, 0.5);\n  backdrop-filter: blur(2px);\n  -webkit-backdrop-filter: blur(2px);\n  animation: fadeIn 0.2s ease;\n}\n.mobile-menu-content {\n  position: relative;\n  width: 320px;\n  max-width: 85vw;\n  height: 100%;\n  background: #ffffff;\n  box-shadow: -4px 0 25px rgba(0, 0, 0, 0.2);\n  display: flex;\n  flex-direction: column;\n  z-index: 1;\n  animation: slideInRight 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n  overflow-y: auto;\n  -webkit-overflow-scrolling: touch;\n}\n.mobile-menu-header {\n  padding: 14px 16px;\n  border-bottom: 1px solid var(--border-light, #e2e8f0);\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n.mobile-menu-title {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  font-weight: 700;\n  font-size: 15px;\n  color: #0f172a;\n}\n.mobile-menu-close-btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 32px;\n  height: 32px;\n  border-radius: 8px;\n  border: 1px solid var(--border-light, #e2e8f0);\n  background: #f1f5f9;\n  color: #475569;\n  cursor: pointer;\n  transition: all 0.15s ease;\n  padding: 0;\n  line-height: 1;\n}\n.mobile-menu-close-btn:hover,\n.mobile-menu-close-btn:active {\n  background: #e2e8f0;\n  color: #0f172a;\n  border-color: #cbd5e1;\n}\n.btn-close-subtle {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 28px;\n  height: 28px;\n  border-radius: 6px;\n  border: 1px solid transparent;\n  background: transparent;\n  color: #64748b;\n  cursor: pointer;\n  font-size: 16px;\n  line-height: 1;\n  transition: all 0.15s ease;\n}\n.btn-close-subtle:hover {\n  background: #f1f5f9;\n  color: #0f172a;\n}\n.mobile-menu-status {\n  padding: 10px 16px;\n  background: #f8fafc;\n  border-bottom: 1px solid var(--border-light, #e2e8f0);\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  font-size: 12.5px;\n  font-weight: 600;\n  color: #334155;\n}\n.mobile-menu-body {\n  padding: 12px 14px 32px 14px;\n  display: flex;\n  flex-direction: column;\n  gap: 6px;\n}\n.mobile-menu-section-label {\n  font-size: 11px;\n  font-weight: 700;\n  text-transform: uppercase;\n  letter-spacing: 0.5px;\n  color: #94a3b8;\n  padding: 6px 4px 2px 4px;\n}\n.mobile-menu-item {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 10px 12px;\n  background: transparent;\n  border: 1px solid transparent;\n  border-radius: 8px;\n  text-align: left;\n  cursor: pointer;\n  transition: all 0.15s ease;\n  width: 100%;\n}\n.mobile-menu-item:hover,\n.mobile-menu-item:active {\n  background: #f1f5f9;\n  border-color: #e2e8f0;\n}\n.mobile-menu-item.active {\n  background: #eff6ff;\n  border-color: #bfdbfe;\n}\n.mobile-menu-item.active .mobile-menu-item-text .primary {\n  color: #1d4ed8;\n}\n.mobile-menu-item-text {\n  display: flex;\n  flex-direction: column;\n  gap: 1px;\n  min-width: 0;\n}\n.mobile-menu-item-text .primary {\n  font-size: 13.5px;\n  font-weight: 600;\n  color: #0f172a;\n}\n.mobile-menu-item-text .secondary {\n  font-size: 11px;\n  color: #64748b;\n  line-height: 1.2;\n}\n.mobile-menu-divider {\n  border: none;\n  border-top: 1px solid var(--border-light, #e2e8f0);\n  margin: 6px 0;\n}\n.mobile-zoom-row {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  padding: 4px 0;\n}\n.mobile-zoom-btn {\n  flex: 1;\n  padding: 6px 4px !important;\n  font-size: 12px !important;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 4px;\n}\n.mobile-zoom-indicator {\n  font-size: 12px;\n  font-weight: 700;\n  color: #334155;\n  min-width: 44px;\n  text-align: center;\n}\n.mobile-export-grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 6px;\n  padding: 4px 0;\n}\n.mobile-action-chip {\n  padding: 8px 6px !important;\n  font-size: 12px !important;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n}\n\n@keyframes slideInRight {\n  from { transform: translateX(100%); }\n  to { transform: translateX(0); }\n}\n\n/* Header Undo/Redo Buttons: Distinct tactile buttons, darker when disabled, light when active */\n.undo-redo-controls {\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n}\n.undo-redo-controls .btn-undo-redo {\n  padding: 6px 10px;\n  height: 32px;\n  min-width: 34px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 6px;\n  border: 1px solid rgba(255, 255, 255, 0.1);\n  background-color: rgba(255, 255, 255, 0.05);\n  transition: all 0.15s ease;\n}\n.undo-redo-controls button:disabled,\n#btnHeaderUndo:disabled,\n#btnHeaderRedo:disabled {\n  opacity: 0.22 !important;\n  color: #334155 !important;\n  border-color: rgba(255, 255, 255, 0.04) !important;\n  background-color: rgba(255, 255, 255, 0.02) !important;\n  cursor: not-allowed !important;\n  pointer-events: none !important;\n}\n.undo-redo-controls button:not(:disabled),\n#btnHeaderUndo:not(:disabled),\n#btnHeaderRedo:not(:disabled) {\n  opacity: 1 !important;\n  color: #f8fafc !important;\n  border-color: rgba(255, 255, 255, 0.12) !important;\n  background-color: rgba(255, 255, 255, 0.06) !important;\n  cursor: pointer !important;\n  pointer-events: auto !important;\n}\n.undo-redo-controls button:not(:disabled):hover,\n#btnHeaderUndo:not(:disabled):hover,\n#btnHeaderRedo:not(:disabled):hover {\n  background-color: rgba(255, 255, 255, 0.14) !important;\n  border-color: rgba(255, 255, 255, 0.25) !important;\n  color: #ffffff !important;\n}\n\n/* Nav Bar Simulation Button */\n.btn-nav-sim {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n}\n.btn-nav-sim.active,\n#btnToggleSituationRunner.active {\n  background-color: rgba(16, 185, 129, 0.18) !important;\n  border-color: rgba(16, 185, 129, 0.4) !important;\n  color: #6ee7b7 !important;\n}\n.btn-nav-sim.active .sim-status-dot,\n#btnToggleSituationRunner.active .sim-status-dot {\n  background: #10b981;\n  box-shadow: 0 0 8px #10b981;\n  animation: pulse-dot 1.2s infinite;\n}\n\n/* Cloud Drive Page Header */\n.drive-page-header {\n  padding: 16px 28px 4px 28px;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n.drive-page-title {\n  font-size: 20px;\n  font-weight: 700;\n  color: #0f172a;\n  letter-spacing: -0.3px;\n  margin: 0;\n}\n\n/* Document Card 3-dot Kebab Menu */\n.card-kebab-wrapper {\n  position: relative;\n  display: inline-flex;\n  z-index: 20;\n}\n\n.btn-card-kebab {\n  width: 24px;\n  height: 24px;\n  border-radius: 6px;\n  background: transparent;\n  border: 1px solid transparent;\n  color: #64748b;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  cursor: pointer;\n  padding: 0;\n  transition: all 0.15s ease;\n}\n\n.btn-card-kebab:hover {\n  background: #e2e8f0;\n  color: #0f172a;\n}\n\n.card-kebab-menu {\n  position: absolute;\n  bottom: calc(100% + 4px);\n  right: 0;\n  min-width: 175px;\n  background: #ffffff;\n  border: 1px solid var(--border-light, #e2e8f0);\n  border-radius: 8px;\n  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.2);\n  padding: 4px;\n  z-index: 1000;\n  display: none;\n}\n\n.card-kebab-menu.show {\n  display: block;\n}\n\n.card-kebab-menu .dropdown-item {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  padding: 7px 10px;\n  font-size: 12px;\n  color: #334155;\n  border-radius: 5px;\n  border: none;\n  background: transparent;\n  width: 100%;\n  text-align: left;\n  cursor: pointer;\n  transition: background 0.15s;\n}\n\n.card-kebab-menu .dropdown-item:hover {\n  background: #f1f5f9;\n  color: #0f172a;\n}\n\n.card-kebab-menu .dropdown-item svg {\n  flex-shrink: 0;\n}\n\n/* Interactive Collaborator Avatars on Document Cards */\n.collaborators-avatars-mini {\n  display: inline-flex;\n  align-items: center;\n  cursor: pointer;\n  transition: transform 0.15s;\n}\n\n.collaborators-avatars-mini:hover {\n  transform: scale(1.08);\n}\n\n/* Custom In-App Validation Highlight */\n.form-input.input-error,\n.form-textarea.input-error,\n.form-select.input-error {\n  border-color: #ef4444 !important;\n  box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.25) !important;\n}\n\n/* ==========================================================================\n   ENTERPRISE ADMIN PORTAL & USER MANAGEMENT STYLES\n   ========================================================================== */\n.btn-admin-nav {\n  color: #7c3aed !important;\n  border-color: rgba(124, 58, 237, 0.3) !important;\n  background: rgba(124, 58, 237, 0.08) !important;\n}\n\n.btn-admin-nav:hover {\n  background: rgba(124, 58, 237, 0.18) !important;\n  border-color: rgba(124, 58, 237, 0.5) !important;\n}\n\n.badge-admin-chip {\n  background: rgba(124, 58, 237, 0.15) !important;\n  color: #7c3aed !important;\n  border: 1px solid rgba(124, 58, 237, 0.3) !important;\n  font-weight: 600;\n}\n\n.admin-portal-tabs {\n  display: flex;\n  gap: 8px;\n  border-bottom: 2px solid #e2e8f0;\n  margin-bottom: 16px;\n}\n\n.admin-tab {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n  padding: 8px 16px;\n  font-size: 13px;\n  font-weight: 600;\n  color: #64748b;\n  background: transparent;\n  border: none;\n  border-bottom: 2px solid transparent;\n  margin-bottom: -2px;\n  cursor: pointer;\n  transition: all 0.15s ease;\n}\n\n.admin-tab:hover {\n  color: #7c3aed;\n}\n\n.admin-tab.active {\n  color: #7c3aed;\n  border-bottom-color: #7c3aed;\n}\n\n.admin-tab-pane {\n  display: none;\n}\n\n.admin-tab-pane.active {\n  display: block;\n}\n\n.admin-table-container {\n  overflow-x: auto;\n  border: 1px solid #e2e8f0;\n  border-radius: 8px;\n  background: #ffffff;\n}\n\n.admin-table {\n  width: 100%;\n  border-collapse: collapse;\n  font-size: 13px;\n  text-align: left;\n}\n\n.admin-table th {\n  background: #f8fafc;\n  color: #475569;\n  font-weight: 600;\n  padding: 10px 14px;\n  border-bottom: 1px solid #e2e8f0;\n  font-size: 12px;\n  text-transform: uppercase;\n  letter-spacing: 0.03em;\n}\n\n.admin-table td {\n  padding: 12px 14px;\n  border-bottom: 1px solid #f1f5f9;\n  vertical-align: middle;\n}\n\n.admin-table tr:last-child td {\n  border-bottom: none;\n}\n\n.admin-table tr:hover td {\n  background: #faf5ff;\n}\n\n.badge-role-admin {\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  padding: 2px 8px;\n  border-radius: 9999px;\n  font-size: 11px;\n  font-weight: 600;\n  background: #ede9fe;\n  color: #6d28d9;\n  border: 1px solid #ddd6fe;\n}\n\n.badge-role-user {\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  padding: 2px 8px;\n  border-radius: 9999px;\n  font-size: 11px;\n  font-weight: 500;\n  background: #f1f5f9;\n  color: #475569;\n  border: 1px solid #e2e8f0;\n}\n\n.badge-status-active {\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  padding: 2px 8px;\n  border-radius: 9999px;\n  font-size: 11px;\n  font-weight: 600;\n  background: #dcfce7;\n  color: #15803d;\n  border: 1px solid #bbf7d0;\n}\n\n.badge-status-disabled {\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  padding: 2px 8px;\n  border-radius: 9999px;\n  font-size: 11px;\n  font-weight: 600;\n  background: #fee2e2;\n  color: #b91c1c;\n  border: 1px solid #fecaca;\n}\n\n.admin-config-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));\n  gap: 16px;\n}\n\n.admin-card {\n  background: #ffffff;\n  border: 1px solid #e2e8f0;\n  border-radius: 8px;\n  overflow: hidden;\n}\n\n.admin-card-header {\n  padding: 10px 14px;\n  background: #f8fafc;\n  border-bottom: 1px solid #e2e8f0;\n}\n\n.admin-card-header h4 {\n  margin: 0;\n  font-size: 13px;\n  font-weight: 700;\n  color: #1e293b;\n}\n\n.admin-card-body {\n  padding: 14px;\n}\n\n/* ==========================================================================\n   ADMIN ELEMENT SUGGESTIONS POOL GRID & CARDS\n   ========================================================================== */\n.admin-suggestions-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));\n  gap: 16px;\n  margin-top: 12px;\n}\n\n.suggestion-card {\n  background: #ffffff;\n  border: 1px solid #e2e8f0;\n  border-radius: 8px;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);\n  transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.suggestion-card:hover {\n  border-color: #cbd5e1;\n  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.07);\n}\n\n.suggestion-card-header {\n  padding: 12px 14px;\n  background: #f8fafc;\n  border-bottom: 1px solid #e2e8f0;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n\n.suggestion-card-title-group {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.suggestion-card-badge {\n  font-size: 11px;\n  font-weight: 700;\n  padding: 3px 9px;\n  border-radius: 12px;\n  color: #ffffff;\n  text-transform: uppercase;\n  letter-spacing: 0.5px;\n}\n\n.suggestion-card-count {\n  font-size: 11px;\n  color: #64748b;\n  font-weight: 500;\n  background: #edf2f7;\n  padding: 2px 7px;\n  border-radius: 10px;\n}\n\n.suggestion-card-desc {\n  padding: 8px 14px 4px 14px;\n  font-size: 11px;\n  color: #64748b;\n  line-height: 1.4;\n}\n\n.suggestion-card-body {\n  padding: 10px 14px 14px 14px;\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  gap: 10px;\n}\n\n.suggestion-chip-list {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 6px;\n  min-height: 48px;\n  max-height: 220px;\n  overflow-y: auto;\n  padding: 2px;\n}\n\n.suggestion-chip {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  padding: 4px 10px;\n  background: #f1f5f9;\n  border: 1px solid #e2e8f0;\n  border-radius: 14px;\n  font-size: 12px;\n  color: #1e293b;\n  line-height: 1.25;\n  transition: all 0.15s ease;\n}\n\n.suggestion-chip:hover {\n  background: #e2e8f0;\n  border-color: #cbd5e1;\n}\n\n.suggestion-chip-text {\n  max-width: 300px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.suggestion-remove-btn {\n  background: none;\n  border: none;\n  cursor: pointer;\n  padding: 0;\n  width: 16px;\n  height: 16px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 50%;\n  color: #94a3b8;\n  font-size: 14px;\n  font-weight: 700;\n  line-height: 1;\n  transition: all 0.15s ease;\n}\n\n.suggestion-remove-btn:hover {\n  background: #fee2e2;\n  color: #ef4444;\n}\n\n.suggestion-add-row {\n  display: flex;\n  gap: 8px;\n  margin-top: auto;\n  padding-top: 8px;\n  border-top: 1px dashed #e2e8f0;\n}\n\n.suggestion-add-input {\n  flex: 1;\n  font-size: 12px;\n  padding: 6px 10px;\n  border: 1px solid #cbd5e1;\n  border-radius: 6px;\n  outline: none;\n  transition: border-color 0.15s;\n}\n\n.suggestion-add-input:focus {\n  border-color: #7c3aed;\n  box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.1);\n}\n\n.suggestion-add-btn {\n  font-size: 12px;\n  padding: 6px 12px;\n  font-weight: 600;\n  color: #ffffff;\n  background: #7c3aed;\n  border: none;\n  border-radius: 6px;\n  cursor: pointer;\n  white-space: nowrap;\n  transition: background 0.15s;\n}\n\n.suggestion-add-btn:hover {\n  background: #6d28d9;\n}\n\n/* ==========================================================================\n   COMPANY DIRECTORY AUTOCOMPLETE & DOCUMENT ACCESS STYLING\n   ========================================================================== */\n.user-autocomplete-wrapper {\n  position: relative;\n  width: 100%;\n}\n\n.user-autocomplete-dropdown {\n  position: absolute;\n  top: calc(100% + 4px);\n  left: 0;\n  right: 0;\n  z-index: 1050;\n  background: #ffffff;\n  border: 1px solid #cbd5e1;\n  border-radius: 8px;\n  box-shadow: 0 12px 24px -4px rgba(15, 23, 42, 0.15), 0 4px 6px -2px rgba(15, 23, 42, 0.05);\n  max-height: 230px;\n  overflow-y: auto;\n  animation: modalPop 0.12s ease-out;\n}\n\n.user-autocomplete-item {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  padding: 8px 12px;\n  border-bottom: 1px solid #f1f5f9;\n  cursor: pointer;\n  transition: background 0.15s ease;\n}\n\n.user-autocomplete-item:last-child {\n  border-bottom: none;\n}\n\n.user-autocomplete-item:hover,\n.user-autocomplete-item.active {\n  background: #f8fafc;\n}\n\n.user-autocomplete-info {\n  flex: 1;\n  min-width: 0;\n}\n\n.user-autocomplete-name {\n  font-size: 13px;\n  font-weight: 600;\n  color: #0f172a;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.user-autocomplete-email {\n  font-size: 11px;\n  color: #64748b;\n  font-family: monospace;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.user-autocomplete-role {\n  font-size: 10px;\n  font-weight: 600;\n  color: #6d28d9;\n  background: #ede9fe;\n  padding: 2px 6px;\n  border-radius: 4px;\n  white-space: nowrap;\n  max-width: 140px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n/* Document Access User Cards in Permissions Table */\n.perm-user-card {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n}\n\n.perm-user-meta {\n  display: flex;\n  flex-direction: column;\n}\n\n.perm-user-name {\n  font-size: 13px;\n  font-weight: 600;\n  color: #0f172a;\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n\n.perm-user-email {\n  font-size: 11px;\n  color: #64748b;\n  font-family: monospace;\n}\n\n.perm-user-role-tag {\n  display: inline-block;\n  font-size: 10px;\n  font-weight: 500;\n  color: #475569;\n  background: #f1f5f9;\n  padding: 1px 5px;\n  border-radius: 3px;\n  margin-top: 2px;\n  width: fit-content;\n}\n\n.collaborators-avatars-mini {\n  display: inline-flex;\n  align-items: center;\n  margin-left: 6px;\n}\n\n.collaborator-avatar-dot {\n  width: 22px;\n  height: 22px;\n  min-width: 22px;\n  min-height: 22px;\n  border-radius: 50%;\n  border: 1.5px solid #ffffff;\n  color: #ffffff;\n  font-size: 9px;\n  font-weight: 700;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  margin-left: -6px;\n  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);\n  cursor: pointer;\n  overflow: hidden;\n  padding: 0;\n  background: transparent;\n  vertical-align: middle;\n  position: relative;\n  z-index: 1;\n  transition: transform 0.15s ease, z-index 0.15s ease;\n}\n\n.collaborator-avatar-dot:hover {\n  transform: scale(1.18);\n  z-index: 10;\n  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25);\n}\n\n.collaborator-avatar-dot:first-child {\n  margin-left: 0;\n}\n\n.collaborator-avatar-dot .avatar,\n.collaborator-avatar-dot .avatar-img {\n  width: 100% !important;\n  height: 100% !important;\n  min-width: 100% !important;\n  min-height: 100% !important;\n  max-width: 100% !important;\n  max-height: 100% !important;\n  border-radius: 50% !important;\n  object-fit: cover !important;\n  display: flex !important;\n  align-items: center !important;\n  justify-content: center !important;\n}\n\n.author-avatar-wrap {\n  width: 26px;\n  height: 26px;\n  min-width: 26px;\n  min-height: 26px;\n  border-radius: 50%;\n  overflow: hidden;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);\n}\n\n.author-avatar-wrap .avatar,\n.author-avatar-wrap .avatar-img {\n  width: 100% !important;\n  height: 100% !important;\n  min-width: 100% !important;\n  min-height: 100% !important;\n  border-radius: 50% !important;\n  object-fit: cover !important;\n}\n\n.gdrive-user-avatar-wrap {\n  width: 36px;\n  height: 36px;\n  min-width: 36px;\n  min-height: 36px;\n  border-radius: 50%;\n  overflow: hidden;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.gdrive-user-avatar-wrap .avatar,\n.gdrive-user-avatar-wrap .avatar-img {\n  width: 100% !important;\n  height: 100% !important;\n  min-width: 100% !important;\n  min-height: 100% !important;\n  border-radius: 50% !important;\n  object-fit: cover !important;\n}\n\n.user-autocomplete-avatar-wrap {\n  width: 28px;\n  height: 28px;\n  min-width: 28px;\n  min-height: 28px;\n  border-radius: 50%;\n  overflow: hidden;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n}\n\n.user-autocomplete-avatar-wrap .avatar,\n.user-autocomplete-avatar-wrap .avatar-img {\n  width: 100% !important;\n  height: 100% !important;\n  min-width: 100% !important;\n  min-height: 100% !important;\n  border-radius: 50% !important;\n  object-fit: cover !important;\n}\n\n/* User Profile & Avatar Styles */\n.avatar-preset-chip {\n  width: 32px;\n  height: 32px;\n  border-radius: 50%;\n  border: 1px solid #cbd5e1;\n  background: #ffffff;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 16px;\n  cursor: pointer;\n  transition: all 0.15s ease;\n  user-select: none;\n}\n\n.avatar-preset-chip:hover {\n  background: #e2e8f0;\n  border-color: #94a3b8;\n  transform: scale(1.1);\n}\n\n.avatar-preset-chip.active {\n  border-color: #2563eb;\n  background: #eff6ff;\n  box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.25);\n  transform: scale(1.12);\n}\n\n.avatar,\n.avatar-sm,\n.avatar-lg,\n.avatar-img,\n.avatar-preview-box,\n.collaborator-avatar-dot {\n  aspect-ratio: 1 / 1 !important;\n  border-radius: 50% !important;\n  object-fit: cover !important;\n  flex-shrink: 0 !important;\n}\n\n.avatar-img-wrap {\n  aspect-ratio: 1 / 1 !important;\n  border-radius: 50% !important;\n  overflow: hidden !important;\n  display: inline-flex !important;\n  align-items: center !important;\n  justify-content: center !important;\n  flex-shrink: 0 !important;\n}\n\n.avatar-img {\n  aspect-ratio: 1 / 1 !important;\n  border-radius: 50% !important;\n  object-fit: cover !important;\n  display: block !important;\n  flex-shrink: 0 !important;\n}\n\n.avatar-img-wrap .avatar-img,\n.avatar-preview-box .avatar-img {\n  width: 100% !important;\n  height: 100% !important;\n  max-width: 100% !important;\n  max-height: 100% !important;\n}\n\n/* Flex & Spacing Utility Classes used in Admin Portal and Modals */\n.flex {\n  display: flex;\n}\n.inline-flex {\n  display: inline-flex;\n}\n.items-center {\n  align-items: center;\n}\n.justify-between {\n  justify-content: space-between;\n}\n.gap-1 {\n  gap: 4px;\n}\n.gap-2 {\n  gap: 8px;\n}\n.gap-3 {\n  gap: 12px;\n}\n.gap-4 {\n  gap: 16px;\n}\n\n/* Strict Admin Console Avatar & Icon Sizing */\n.admin-portal-view svg {\n  flex-shrink: 0;\n}\n.admin-table .avatar,\n.admin-table .avatar-img-wrap,\n.admin-table .avatar-img {\n  width: 32px !important;\n  height: 32px !important;\n  min-width: 32px !important;\n  min-height: 32px !important;\n  max-width: 32px !important;\n  max-height: 32px !important;\n  border-radius: 50% !important;\n  object-fit: cover !important;\n  flex-shrink: 0 !important;\n}\n\n.admin-user-row-clickable {\n  cursor: pointer;\n  transition: background 0.12s ease;\n}\n\n.admin-user-row-clickable:hover td {\n  background-color: #f8fafc;\n}\n\n.profile-avatar-editor {\n  background: #f8fafc;\n  padding: 14px;\n  border-radius: 8px;\n  border: 1px solid #e2e8f0;\n}\n\n.avatar-preview-box {\n  width: 72px;\n  height: 72px;\n  font-size: 28px;\n  flex-shrink: 0;\n  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);\n  border-radius: 50%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  overflow: hidden;\n  position: relative;\n}\n\n/* ==========================================================================\n   GOOGLE DRIVE SHARING MODAL DESIGN & STYLING\n   ========================================================================== */\n.gdrive-share-modal {\n  width: 540px !important;\n  max-width: 95vw !important;\n  border-radius: 20px !important;\n  background: #ffffff !important;\n  box-shadow: 0 20px 40px -8px rgba(15, 23, 42, 0.28), 0 0 0 1px rgba(0, 0, 0, 0.05) !important;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  color: #1f2937;\n  font-family: inherit;\n}\n\n.gdrive-share-header {\n  padding: 22px 24px 14px 24px;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  border-bottom: none;\n}\n\n.gdrive-share-title-wrap {\n  flex: 1;\n  min-width: 0;\n}\n\n.gdrive-share-title {\n  font-size: 20px !important;\n  font-weight: 500 !important;\n  color: #1f2937 !important;\n  margin: 0 !important;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  letter-spacing: -0.2px;\n}\n\n.gdrive-share-header-actions {\n  display: flex;\n  align-items: center;\n  gap: 4px;\n  flex-shrink: 0;\n}\n\n.gdrive-icon-btn {\n  width: 36px;\n  height: 36px;\n  border-radius: 50%;\n  border: none;\n  background: transparent;\n  color: #5f6368;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  cursor: pointer;\n  transition: background 0.15s ease, color 0.15s ease;\n  font-size: 16px;\n  padding: 0;\n}\n\n.gdrive-icon-btn:hover {\n  background: #f1f3f4;\n  color: #1f2937;\n}\n\n.gdrive-share-body {\n  padding: 0 24px 12px 24px;\n  max-height: calc(85vh - 145px);\n  overflow-y: auto;\n}\n\n/* Outlined Search Box */\n.gdrive-search-box-wrap {\n  position: relative;\n  margin-top: 4px;\n  margin-bottom: 12px;\n}\n\n.gdrive-search-box {\n  border: 1px solid #747775;\n  border-radius: 8px;\n  background: #ffffff;\n  transition: border-color 0.15s ease, box-shadow 0.15s ease;\n  display: flex;\n  align-items: center;\n}\n\n.gdrive-search-box:focus-within {\n  border-color: #0b57d0;\n  box-shadow: 0 0 0 1px #0b57d0;\n}\n\n.gdrive-search-input {\n  width: 100%;\n  height: 48px;\n  padding: 0 16px;\n  border: none;\n  outline: none;\n  background: transparent;\n  font-size: 14px;\n  color: #1f2937;\n  font-family: inherit;\n}\n\n.gdrive-search-input::placeholder {\n  color: #444746;\n  opacity: 1;\n}\n\n.gdrive-autocomplete {\n  position: absolute;\n  top: calc(100% + 4px);\n  left: 0;\n  right: 0;\n  border-radius: 8px;\n  border: 1px solid #cbd5e1;\n  box-shadow: 0 12px 28px rgba(15, 23, 42, 0.16);\n  background: #ffffff;\n  z-index: 1200;\n  max-height: 230px;\n  overflow-y: auto;\n}\n\n/* Active Inviting / Add Panel */\n.gdrive-invite-panel {\n  border: 1px solid #c4c7c5;\n  border-radius: 8px;\n  padding: 12px 14px;\n  background: #f8fafc;\n  margin-bottom: 14px;\n  animation: modalPop 0.12s ease-out;\n}\n\n.gdrive-invite-main-row {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 10px;\n  margin-bottom: 10px;\n  flex-wrap: wrap;\n}\n\n.gdrive-recipient-chip {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  background: #e8f0fe;\n  color: #0b57d0;\n  border-radius: 9999px;\n  padding: 4px 12px;\n  font-size: 13px;\n  font-weight: 500;\n  max-width: 260px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.btn-chip-remove {\n  border: none;\n  background: transparent;\n  color: #0b57d0;\n  cursor: pointer;\n  font-size: 12px;\n  padding: 0 2px;\n  line-height: 1;\n}\n\n.btn-chip-remove:hover {\n  color: #d93025;\n}\n\n.gdrive-invite-selectors {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.gdrive-role-dropdown {\n  height: 34px;\n  padding: 0 10px;\n  border-radius: 6px;\n  border: 1px solid #cbd5e1;\n  background: #ffffff;\n  font-size: 13px;\n  font-weight: 500;\n  color: #1f2937;\n  cursor: pointer;\n  outline: none;\n}\n\n.gdrive-role-dropdown:focus {\n  border-color: #0b57d0;\n}\n\n.gdrive-sub-selector {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  margin-bottom: 8px;\n}\n\n.gdrive-sub-label {\n  font-size: 12px;\n  font-weight: 500;\n  color: #64748b;\n  min-width: 55px;\n}\n\n.gdrive-message-wrap {\n  margin-bottom: 10px;\n}\n\n.gdrive-message-input {\n  width: 100%;\n  height: 36px;\n  padding: 0 12px;\n  border: 1px solid #cbd5e1;\n  border-radius: 6px;\n  font-size: 13px;\n  background: #ffffff;\n  color: #1f2937;\n  outline: none;\n}\n\n.gdrive-message-input:focus {\n  border-color: #0b57d0;\n}\n\n.gdrive-invite-actions {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  gap: 8px;\n}\n\n.btn-gdrive-cancel {\n  height: 34px;\n  padding: 0 16px;\n  border-radius: 9999px;\n  border: none;\n  background: transparent;\n  color: #5f6368;\n  font-size: 13px;\n  font-weight: 500;\n  cursor: pointer;\n  transition: background 0.15s ease;\n}\n\n.btn-gdrive-cancel:hover {\n  background: #f1f3f4;\n  color: #1f2937;\n}\n\n.btn-gdrive-send {\n  height: 34px;\n  padding: 0 20px;\n  border-radius: 9999px;\n  border: none;\n  background: #0b57d0;\n  color: #ffffff;\n  font-size: 13px;\n  font-weight: 500;\n  cursor: pointer;\n  transition: background 0.15s ease;\n}\n\n.btn-gdrive-send:hover {\n  background: #0842a0;\n}\n\n/* Section Styling */\n.gdrive-section {\n  margin-top: 20px;\n}\n\n.gdrive-section-title {\n  font-size: 14px !important;\n  font-weight: 500 !important;\n  color: #1f2937 !important;\n  margin: 0 0 10px 0 !important;\n}\n\n/* People with access list */\n.gdrive-user-list {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n}\n\n.gdrive-user-row {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 6px 0;\n  gap: 12px;\n}\n\n.gdrive-user-left {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  min-width: 0;\n  flex: 1;\n}\n\n.gdrive-user-meta {\n  display: flex;\n  flex-direction: column;\n  min-width: 0;\n}\n\n.gdrive-user-name {\n  font-size: 14px;\n  font-weight: 500;\n  color: #1f2937;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n\n.gdrive-user-email {\n  font-size: 12px;\n  color: #5f6368;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.gdrive-user-right {\n  display: flex;\n  align-items: center;\n  flex-shrink: 0;\n}\n\n.gdrive-owner-label {\n  font-size: 13px;\n  color: #5f6368;\n  padding-right: 10px;\n  font-weight: 400;\n}\n\n.gdrive-collaborator-role-select {\n  border: 1px solid transparent;\n  background: transparent;\n  font-size: 13px;\n  font-weight: 500;\n  color: #1f2937;\n  padding: 6px 10px;\n  border-radius: 6px;\n  cursor: pointer;\n  outline: none;\n  transition: background 0.15s ease, border-color 0.15s ease;\n}\n\n.gdrive-collaborator-role-select:hover {\n  background: #f1f3f4;\n}\n\n.gdrive-collaborator-role-select:focus {\n  border-color: #0b57d0;\n  background: #ffffff;\n}\n\n/* General access row */\n.gdrive-general-access-row {\n  display: flex;\n  align-items: center;\n  gap: 14px;\n  padding: 4px 0;\n}\n\n.gdrive-general-access-icon {\n  width: 36px;\n  height: 36px;\n  border-radius: 50%;\n  background: #f1f3f4;\n  color: #444746;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n}\n\n.gdrive-general-access-info {\n  display: flex;\n  flex-direction: column;\n  min-width: 0;\n}\n\n.gdrive-general-access-header {\n  display: flex;\n  align-items: center;\n}\n\n.gdrive-access-selector {\n  border: none;\n  background: transparent;\n  font-size: 14px;\n  font-weight: 500;\n  color: #1f2937;\n  cursor: pointer;\n  padding: 0;\n  outline: none;\n  width: fit-content;\n}\n\n.gdrive-access-selector:hover {\n  text-decoration: underline;\n}\n\n.gdrive-general-access-desc {\n  font-size: 12px;\n  color: #5f6368;\n  margin-top: 2px;\n}\n\n/* Footer Action Bar */\n.gdrive-share-footer {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 18px 24px 22px 24px;\n  background: #ffffff;\n  border-top: none;\n}\n\n.btn-gdrive-copy-link {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n  height: 36px;\n  padding: 0 16px;\n  border-radius: 9999px;\n  border: 1px solid #747775;\n  background: transparent;\n  color: #0b57d0;\n  font-size: 14px;\n  font-weight: 500;\n  cursor: pointer;\n  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n}\n\n.btn-gdrive-copy-link:hover {\n  background: #f0f7ff;\n  border-color: #0b57d0;\n}\n\n.btn-gdrive-done {\n  height: 36px;\n  padding: 0 24px;\n  border-radius: 9999px;\n  border: none;\n  background: #0b57d0;\n  color: #ffffff;\n  font-size: 14px;\n  font-weight: 500;\n  cursor: pointer;\n  transition: background 0.15s ease, box-shadow 0.15s ease;\n}\n\n.btn-gdrive-done:hover {\n  background: #0842a0;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);\n}\n\n/* ==========================================================================\n   ADMIN PORTAL TAB VIEW & DRAGGABLE TAB REORDERING STYLES\n   ========================================================================== */\n.admin-portal-view {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  overflow-y: auto;\n  position: relative;\n  background: var(--bg-main, #f8fafc);\n  padding: 24px 20px;\n  min-height: 0;\n}\n\n.admin-portal-container {\n  max-width: 1100px;\n  width: 100%;\n  margin: 0 auto;\n  background: #ffffff;\n  border-radius: 12px;\n  border: 1px solid var(--border-light, #e2e8f0);\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);\n  padding: 24px;\n}\n\n.admin-portal-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n}\n\n.admin-portal-footer {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  border-top: 1px solid var(--border-light, #e2e8f0);\n  padding-top: 16px;\n  margin-top: 20px;\n}\n\n/* Admin Tab Styling in Top Tab Bar */\n.diagram-tab.admin-tab-item {\n  color: #7c3aed;\n  border-color: rgba(124, 58, 237, 0.25);\n  background: rgba(124, 58, 237, 0.03);\n}\n\n.diagram-tab.admin-tab-item:hover {\n  background: rgba(124, 58, 237, 0.09);\n  color: #6d28d9;\n}\n\n.diagram-tab.admin-tab-item.active {\n  color: #7c3aed !important;\n  background: #ffffff !important;\n  border-bottom: 2px solid #7c3aed !important;\n  font-weight: 600;\n}\n\n.diagram-tab.admin-tab-item.active .tab-icon {\n  stroke: #7c3aed !important;\n}\n\n.tab-title-admin {\n  color: #7c3aed !important;\n  font-weight: 600 !important;\n}\n\n/* Draggable Tabs to Reorder */\n.diagram-tab[draggable=\"true\"] {\n  cursor: grab;\n  user-select: none;\n}\n\n.diagram-tab[draggable=\"true\"]:active {\n  cursor: grabbing;\n}\n\n.diagram-tab.tab-dragging {\n  opacity: 0.45;\n  cursor: grabbing;\n  transform: scale(0.97);\n}\n\n.diagram-tab.drop-target-left {\n  border-left: 3px solid var(--primary, #2563eb) !important;\n}\n\n.diagram-tab.drop-target-right {\n  border-right: 3px solid var(--primary, #2563eb) !important;\n}\n\n.diagram-tab.admin-tab-item.drop-target-left {\n  border-left: 3px solid #7c3aed !important;\n}\n\n.diagram-tab.admin-tab-item.drop-target-right {\n  border-right: 3px solid #7c3aed !important;\n}\n\n/* ==========================================================================\n   ON-PREMISE UNLICENSED READ-ONLY MODE BANNER\n   ========================================================================== */\n.unlicensed-banner {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  padding: 8px 18px;\n  background: linear-gradient(90deg, #92400e 0%, #b45309 50%, #92400e 100%);\n  color: #ffffff;\n  font-size: 12.5px;\n  line-height: 1.4;\n  border-bottom: 1px solid #f59e0b;\n  flex-shrink: 0;\n  z-index: 40;\n  flex-wrap: wrap;\n}\n\n.unlicensed-banner-left {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  flex-wrap: wrap;\n}\n\n.unlicensed-badge {\n  display: inline-flex;\n  align-items: center;\n  padding: 2px 8px;\n  border-radius: 999px;\n  background: #fef3c7;\n  color: #78350f;\n  font-size: 10.5px;\n  font-weight: 800;\n  letter-spacing: 0.04em;\n  text-transform: uppercase;\n  white-space: nowrap;\n}\n\n.unlicensed-banner-actions {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  flex-wrap: wrap;\n}\n\n.unlicensed-btn-portal {\n  background: rgba(255, 255, 255, 0.16);\n  color: #ffffff !important;\n  border: 1px solid rgba(255, 255, 255, 0.35);\n  text-decoration: none;\n  font-weight: 600;\n}\n\n.unlicensed-btn-portal:hover {\n  background: rgba(255, 255, 255, 0.25);\n}\n\n.unlicensed-btn-activate {\n  background: #ffffff;\n  color: #78350f !important;\n  border: 1px solid #ffffff;\n  font-weight: 700;\n}\n\n.unlicensed-btn-activate:hover {\n  background: #fef3c7;\n}\n\n\n",
    "index.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n  <meta name=\"theme-color\" content=\"#0b1329\">\n  <meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content=\"#0b1329\">\n  <meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#0b1329\">\n  <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"Bowtie RISK\">\n  <meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\">\n  <meta http-equiv=\"Pragma\" content=\"no-cache\">\n  <meta http-equiv=\"Expires\" content=\"0\">\n  <title>Bowtie RISK - Risk Assessment System</title>\n  <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg?v=1.1.5\">\n  <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png?v=1.1.5\">\n  <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png?v=1.1.5\">\n  <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png?v=1.1.5\">\n  <link rel=\"shortcut icon\" href=\"/favicon.ico?v=1.1.5\">\n  <link rel=\"stylesheet\" href=\"/css/style.css?v=1.1.10\">\n  <!-- Google Fonts: Inter -->\n  <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n  <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n  <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n</head>\n<body>\n  <!-- Auth Wall Screen (Google Docs style secure login/signup gate) -->\n  <div id=\"authWallScreen\" class=\"auth-wall-screen\" style=\"display: none;\">\n    <div class=\"auth-wall-card\">\n      <div class=\"auth-wall-logo\">\n        <svg width=\"180\" height=\"50\" viewBox=\"0 0 632 207\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n          <circle cx=\"107\" cy=\"105\" r=\"91\" stroke=\"white\" stroke-width=\"18\" fill=\"none\" />\n          <path d=\"M101.437 91.7129V118.886L49.0684 162.465V128.48L73.3848 105.189L49.0684 82.1182V48.0605L101.437 91.7129ZM140.542 105.189L165.005 128.48V162.465L112.637 118.886V91.7129L165.005 48.0605V82.1182L140.542 105.189Z\" fill=\"white\" />\n          <path d=\"M220.227 99V5.90909H254.318C260.924 5.90909 266.394 7 270.727 9.18182C275.061 11.3333 278.303 14.2576 280.455 17.9545C282.606 21.6212 283.682 25.7576 283.682 30.3636C283.682 34.2424 282.97 37.5151 281.545 40.1818C280.121 42.8182 278.212 44.9394 275.818 46.5455C273.455 48.1212 270.848 49.2727 268 50V50.9091C271.091 51.0606 274.106 52.0606 277.045 53.9091C280.015 55.7273 282.47 58.3182 284.409 61.6818C286.348 65.0455 287.318 69.1364 287.318 73.9545C287.318 78.7121 286.197 82.9848 283.955 86.7727C281.742 90.5303 278.318 93.5152 273.682 95.7273C269.045 97.9091 263.121 99 255.909 99H220.227ZM234.273 86.9545H254.545C261.273 86.9545 266.091 85.6515 269 83.0455C271.909 80.4394 273.364 77.1818 273.364 73.2727C273.364 70.3333 272.621 67.6364 271.136 65.1818C269.652 62.7273 267.53 60.7727 264.773 59.3182C262.045 57.8636 258.803 57.1364 255.045 57.1364H234.273V86.9545ZM234.273 46.1818H253.091C256.242 46.1818 259.076 45.5758 261.591 44.3636C264.136 43.1515 266.152 41.4545 267.636 39.2727C269.152 37.0606 269.909 34.4545 269.909 31.4545C269.909 27.6061 268.561 24.3788 265.864 21.7727C263.167 19.1667 259.03 17.8636 253.455 17.8636H234.273V46.1818ZM332.295 100.409C325.75 100.409 320.038 98.9091 315.159 95.9091C310.28 92.9091 306.492 88.7121 303.795 83.3182C301.098 77.9242 299.75 71.6212 299.75 64.4091C299.75 57.1667 301.098 50.8333 303.795 45.4091C306.492 39.9848 310.28 35.7727 315.159 32.7727C320.038 29.7727 325.75 28.2727 332.295 28.2727C338.841 28.2727 344.553 29.7727 349.432 32.7727C354.311 35.7727 358.098 39.9848 360.795 45.4091C363.492 50.8333 364.841 57.1667 364.841 64.4091C364.841 71.6212 363.492 77.9242 360.795 83.3182C358.098 88.7121 354.311 92.9091 349.432 95.9091C344.553 98.9091 338.841 100.409 332.295 100.409ZM332.341 89C336.583 89 340.098 87.8788 342.886 85.6364C345.674 83.3939 347.735 80.4091 349.068 76.6818C350.432 72.9545 351.114 68.8485 351.114 64.3636C351.114 59.9091 350.432 55.8182 349.068 52.0909C347.735 48.3333 345.674 45.3182 342.886 43.0455C340.098 40.7727 336.583 39.6364 332.341 39.6364C328.068 39.6364 324.523 40.7727 321.705 43.0455C318.917 45.3182 316.841 48.3333 315.477 52.0909C314.144 55.8182 313.477 59.9091 313.477 64.3636C313.477 68.8485 314.144 72.9545 315.477 76.6818C316.841 80.4091 318.917 83.3939 321.705 85.6364C324.523 87.8788 328.068 89 332.341 89ZM393.023 99L372.477 29.1818H386.523L400.205 80.4545H400.886L414.614 29.1818H428.659L442.295 80.2273H442.977L456.568 29.1818H470.614L450.114 99H436.25L422.068 48.5909H421.023L406.841 99H393.023ZM516.102 29.1818V40.0909H477.966V29.1818H516.102ZM488.193 12.4545H501.784V78.5C501.784 81.1364 502.178 83.1212 502.966 84.4545C503.754 85.7576 504.769 86.6515 506.011 87.1364C507.284 87.5909 508.663 87.8182 510.148 87.8182C511.239 87.8182 512.193 87.7424 513.011 87.5909C513.83 87.4394 514.466 87.3182 514.92 87.2273L517.375 98.4545C516.587 98.7576 515.466 99.0606 514.011 99.3636C512.557 99.697 510.739 99.8788 508.557 99.9091C504.981 99.9697 501.648 99.3333 498.557 98C495.466 96.6667 492.966 94.6061 491.057 91.8182C489.148 89.0303 488.193 85.5303 488.193 81.3182V12.4545ZM531.136 99V29.1818H544.727V99H531.136ZM538 18.4091C535.636 18.4091 533.606 17.6212 531.909 16.0454C530.242 14.4394 529.409 12.5303 529.409 10.3182C529.409 8.07575 530.242 6.16666 531.909 4.5909C533.606 2.98485 535.636 2.18182 538 2.18182C540.364 2.18182 542.379 2.98485 544.045 4.5909C545.742 6.16666 546.591 8.07575 546.591 10.3182C546.591 12.5303 545.742 14.4394 544.045 16.0454C542.379 17.6212 540.364 18.4091 538 18.4091ZM593.102 100.409C586.223 100.409 580.299 98.9394 575.33 96C570.39 93.0303 566.572 88.8636 563.875 83.5C561.208 78.1061 559.875 71.7879 559.875 64.5455C559.875 57.3939 561.208 51.0909 563.875 45.6364C566.572 40.1818 570.33 35.9242 575.148 32.8636C579.996 29.803 585.663 28.2727 592.148 28.2727C596.087 28.2727 599.905 28.9242 603.602 30.2273C607.299 31.5303 610.617 33.5758 613.557 36.3636C616.496 39.1515 618.814 42.7727 620.511 47.2273C622.208 51.6515 623.057 57.0303 623.057 63.3636V68.1818H567.557V58H609.739C609.739 54.4242 609.011 51.2576 607.557 48.5C606.102 45.7121 604.057 43.5151 601.42 41.9091C598.814 40.303 595.754 39.5 592.239 39.5C588.42 39.5 585.087 40.4394 582.239 42.3182C579.42 44.1667 577.239 46.5909 575.693 49.5909C574.178 52.5606 573.42 55.7879 573.42 59.2727V67.2273C573.42 71.8939 574.239 75.8636 575.875 79.1364C577.542 82.4091 579.86 84.9091 582.83 86.6364C585.799 88.3333 589.269 89.1818 593.239 89.1818C595.814 89.1818 598.163 88.8182 600.284 88.0909C602.405 87.3333 604.239 86.2121 605.784 84.7273C607.33 83.2424 608.511 81.4091 609.33 79.2273L622.193 81.5455C621.163 85.3333 619.314 88.6515 616.648 91.5C614.011 94.3182 610.693 96.5152 606.693 98.0909C602.723 99.6364 598.193 100.409 593.102 100.409Z\" fill=\"white\" />\n          <path d=\"M221.045 205V111.909H259.5C266.47 111.909 272.485 113.167 277.545 115.682C282.636 118.167 286.561 121.742 289.318 126.409C292.076 131.045 293.455 136.545 293.455 142.909C293.455 149.364 292.045 154.848 289.227 159.364C286.409 163.848 282.409 167.273 277.227 169.636C272.045 171.97 265.909 173.136 258.818 173.136H234.5V155.409H254.636C258.03 155.409 260.864 154.97 263.136 154.091C265.439 153.182 267.182 151.818 268.364 150C269.545 148.152 270.136 145.788 270.136 142.909C270.136 140.03 269.545 137.652 268.364 135.773C267.182 133.864 265.439 132.439 263.136 131.5C260.833 130.53 258 130.045 254.636 130.045H243.545V205H221.045ZM273.455 162.455L296.636 205H272.091L249.364 162.455H273.455ZM328.295 111.909V205H305.795V111.909H328.295ZM392.739 139.818C392.436 136.485 391.087 133.894 388.693 132.045C386.33 130.167 382.951 129.227 378.557 129.227C375.648 129.227 373.223 129.606 371.284 130.364C369.345 131.121 367.89 132.167 366.92 133.5C365.951 134.803 365.451 136.303 365.42 138C365.36 139.394 365.633 140.621 366.239 141.682C366.875 142.742 367.784 143.682 368.966 144.5C370.178 145.288 371.633 145.985 373.33 146.591C375.027 147.197 376.936 147.727 379.057 148.182L387.057 150C391.663 151 395.723 152.333 399.239 154C402.784 155.667 405.754 157.652 408.148 159.955C410.572 162.258 412.405 164.909 413.648 167.909C414.89 170.909 415.527 174.273 415.557 178C415.527 183.879 414.042 188.924 411.102 193.136C408.163 197.348 403.936 200.576 398.42 202.818C392.936 205.061 386.314 206.182 378.557 206.182C370.769 206.182 363.981 205.015 358.193 202.682C352.405 200.348 347.905 196.803 344.693 192.045C341.481 187.288 339.83 181.273 339.739 174H361.284C361.466 177 362.269 179.5 363.693 181.5C365.117 183.5 367.072 185.015 369.557 186.045C372.072 187.076 374.981 187.591 378.284 187.591C381.314 187.591 383.89 187.182 386.011 186.364C388.163 185.545 389.814 184.409 390.966 182.955C392.117 181.5 392.708 179.833 392.739 177.955C392.708 176.197 392.163 174.697 391.102 173.455C390.042 172.182 388.405 171.091 386.193 170.182C384.011 169.242 381.223 168.379 377.83 167.591L368.102 165.318C360.042 163.47 353.693 160.485 349.057 156.364C344.42 152.212 342.117 146.606 342.148 139.545C342.117 133.788 343.663 128.742 346.784 124.409C349.905 120.076 354.223 116.697 359.739 114.273C365.254 111.848 371.542 110.636 378.602 110.636C385.814 110.636 392.072 111.864 397.375 114.318C402.708 116.742 406.845 120.152 409.784 124.545C412.723 128.939 414.223 134.03 414.284 139.818H392.739ZM426.92 205V111.909H449.42V151.273H450.648L481.42 111.909H507.92L474.92 153.455L508.511 205H481.602L458.42 168.409L449.42 179.773V205H426.92Z\" fill=\"#e00000\" />\n        </svg>\n      </div>\n      <div class=\"auth-wall-header\">\n        <h2 class=\"auth-wall-title\">Secure Workspace Sign In</h2>\n        <p class=\"auth-wall-desc\">Sign in with your Bowtie RISK enterprise credentials to access protected risk assessments.</p>\n      </div>\n\n      <div class=\"auth-wall-tabs\">\n        <button id=\"wallTabLoginBtn\" class=\"auth-wall-tab-btn active\" type=\"button\">Sign In</button>\n        <button id=\"wallTabSignupBtn\" class=\"auth-wall-tab-btn\" type=\"button\">Create Account</button>\n      </div>\n\n      <div id=\"authWallError\" class=\"auth-wall-error\" style=\"display: none;\"></div>\n      <div id=\"authWallSuccess\" class=\"auth-wall-success\" style=\"display: none;\"></div>\n\n      <!-- Sign In Form -->\n      <form id=\"wallLoginForm\" class=\"auth-wall-form\" novalidate>\n        <div class=\"form-group\">\n          <label for=\"wallLoginEmail\">Email Address</label>\n          <input type=\"email\" id=\"wallLoginEmail\" class=\"form-input\" placeholder=\"name@company.com\" autocomplete=\"email\">\n        </div>\n        <div class=\"form-group\">\n          <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;\">\n            <label for=\"wallLoginPassword\" style=\"margin-bottom: 0;\">Password</label>\n            <a href=\"#\" id=\"linkWallForgotPassword\" class=\"auth-link\" style=\"font-size: 12px; font-weight: normal; text-transform: none;\">Forgot password?</a>\n          </div>\n          <input type=\"password\" id=\"wallLoginPassword\" class=\"form-input\" placeholder=\"••••••••\" autocomplete=\"current-password\">\n        </div>\n        <button type=\"submit\" id=\"btnWallSubmitLogin\" class=\"btn btn-primary btn-block\">Sign In to Bowtie RISK</button>\n      </form>\n\n      <!-- Sign Up Form -->\n      <form id=\"wallSignupForm\" class=\"auth-wall-form\" style=\"display: none;\" novalidate>\n        <div class=\"form-group\">\n          <label for=\"wallSignupName\">Full Name</label>\n          <input type=\"text\" id=\"wallSignupName\" class=\"form-input\" placeholder=\"e.g. David J. Bowtie\" autocomplete=\"name\">\n        </div>\n        <div class=\"form-group\">\n          <label for=\"wallSignupEmail\">Corporate Email</label>\n          <input type=\"email\" id=\"wallSignupEmail\" class=\"form-input\" placeholder=\"name@company.com\" autocomplete=\"email\">\n        </div>\n        <div class=\"form-group\">\n          <label for=\"wallSignupPassword\">Create Password</label>\n          <input type=\"password\" id=\"wallSignupPassword\" class=\"form-input\" placeholder=\"Min. 4 characters\" autocomplete=\"new-password\">\n        </div>\n        <div class=\"form-group\">\n          <label for=\"wallSignupOrg\">Department / Business Unit</label>\n          <input type=\"text\" id=\"wallSignupOrg\" class=\"form-input\" placeholder=\"e.g. Offshore Risk Engineering\" autocomplete=\"organization\">\n        </div>\n        <button type=\"submit\" id=\"btnWallSubmitSignup\" class=\"btn btn-primary btn-block\">Create Protected Account</button>\n      </form>\n\n      <!-- Forgot Password Step 1: Request Code -->\n      <form id=\"wallForgotForm\" class=\"auth-wall-form\" style=\"display: none;\" novalidate>\n        <p class=\"text-sm\" style=\"color: #cbd5e1; margin-bottom: 14px;\">Enter your registered account email. We will generate a secure 6-digit verification code to reset your password.</p>\n        <div class=\"form-group\">\n          <label for=\"wallForgotEmail\">Registered Email</label>\n          <input type=\"email\" id=\"wallForgotEmail\" class=\"form-input\" placeholder=\"name@company.com\" autocomplete=\"email\">\n        </div>\n        <button type=\"submit\" id=\"btnWallSubmitForgot\" class=\"btn btn-primary btn-block\">Send Verification Code</button>\n        <div style=\"text-align: center; margin-top: 14px;\">\n          <a href=\"#\" id=\"linkWallBackToLoginFromForgot\" class=\"auth-link\" style=\"font-size: 13px;\">← Back to Sign In</a>\n        </div>\n      </form>\n\n      <!-- Forgot Password Step 2: Verification Code & Reset Password -->\n      <form id=\"wallResetForm\" class=\"auth-wall-form\" style=\"display: none;\" novalidate>\n        <div class=\"form-group\">\n          <label for=\"wallResetEmail\">Account Email</label>\n          <input type=\"email\" id=\"wallResetEmail\" class=\"form-input\" placeholder=\"name@company.com\" readonly style=\"opacity: 0.85; background: rgba(15, 23, 42, 0.6);\">\n        </div>\n        <div class=\"form-group\">\n          <label for=\"wallResetCode\">6-Digit Verification Code</label>\n          <input type=\"text\" id=\"wallResetCode\" class=\"form-input\" placeholder=\"123456\" maxlength=\"6\" style=\"letter-spacing: 4px; font-weight: bold; font-family: monospace; font-size: 16px; text-align: center;\" autocomplete=\"one-time-code\">\n        </div>\n        <div class=\"form-group\">\n          <label for=\"wallResetNewPassword\">New Password</label>\n          <input type=\"password\" id=\"wallResetNewPassword\" class=\"form-input\" placeholder=\"Min. 4 characters\" autocomplete=\"new-password\">\n        </div>\n        <div class=\"form-group\">\n          <label for=\"wallResetConfirmPassword\">Confirm New Password</label>\n          <input type=\"password\" id=\"wallResetConfirmPassword\" class=\"form-input\" placeholder=\"Re-enter new password\" autocomplete=\"new-password\">\n        </div>\n        <button type=\"submit\" id=\"btnWallSubmitReset\" class=\"btn btn-primary btn-block\">Set New Password & Sign In</button>\n        <div style=\"text-align: center; margin-top: 14px; display: flex; justify-content: space-between; font-size: 12.5px;\">\n          <a href=\"#\" id=\"linkWallResendCode\" class=\"auth-link\">Request new code</a>\n          <a href=\"#\" id=\"linkWallBackToLoginFromReset\" class=\"auth-link\">← Cancel</a>\n        </div>\n      </form>\n    </div>\n  </div>\n\n  <div id=\"app\" class=\"app-container\">\n    <!-- Top Navigation Bar -->\n    <header class=\"top-nav\">\n      <div class=\"nav-left\">\n        <!-- Exact Bowtie RISK SVG Logo -->\n        <div class=\"brand-logo\" id=\"navBrandLogo\" role=\"button\" tabindex=\"0\" title=\"Bowtie RISK Platform - Document View\" aria-label=\"Go to Documents\">\n          <svg width=\"145\" height=\"40\" viewBox=\"0 0 632 207\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"107\" cy=\"105\" r=\"91\" stroke=\"white\" stroke-width=\"18\" fill=\"none\" />\n            <path d=\"M101.437 91.7129V118.886L49.0684 162.465V128.48L73.3848 105.189L49.0684 82.1182V48.0605L101.437 91.7129ZM140.542 105.189L165.005 128.48V162.465L112.637 118.886V91.7129L165.005 48.0605V82.1182L140.542 105.189Z\" fill=\"white\" />\n            <path d=\"M220.227 99V5.90909H254.318C260.924 5.90909 266.394 7 270.727 9.18182C275.061 11.3333 278.303 14.2576 280.455 17.9545C282.606 21.6212 283.682 25.7576 283.682 30.3636C283.682 34.2424 282.97 37.5151 281.545 40.1818C280.121 42.8182 278.212 44.9394 275.818 46.5455C273.455 48.1212 270.848 49.2727 268 50V50.9091C271.091 51.0606 274.106 52.0606 277.045 53.9091C280.015 55.7273 282.47 58.3182 284.409 61.6818C286.348 65.0455 287.318 69.1364 287.318 73.9545C287.318 78.7121 286.197 82.9848 283.955 86.7727C281.742 90.5303 278.318 93.5152 273.682 95.7273C269.045 97.9091 263.121 99 255.909 99H220.227ZM234.273 86.9545H254.545C261.273 86.9545 266.091 85.6515 269 83.0455C271.909 80.4394 273.364 77.1818 273.364 73.2727C273.364 70.3333 272.621 67.6364 271.136 65.1818C269.652 62.7273 267.53 60.7727 264.773 59.3182C262.045 57.8636 258.803 57.1364 255.045 57.1364H234.273V86.9545ZM234.273 46.1818H253.091C256.242 46.1818 259.076 45.5758 261.591 44.3636C264.136 43.1515 266.152 41.4545 267.636 39.2727C269.152 37.0606 269.909 34.4545 269.909 31.4545C269.909 27.6061 268.561 24.3788 265.864 21.7727C263.167 19.1667 259.03 17.8636 253.455 17.8636H234.273V46.1818ZM332.295 100.409C325.75 100.409 320.038 98.9091 315.159 95.9091C310.28 92.9091 306.492 88.7121 303.795 83.3182C301.098 77.9242 299.75 71.6212 299.75 64.4091C299.75 57.1667 301.098 50.8333 303.795 45.4091C306.492 39.9848 310.28 35.7727 315.159 32.7727C320.038 29.7727 325.75 28.2727 332.295 28.2727C338.841 28.2727 344.553 29.7727 349.432 32.7727C354.311 35.7727 358.098 39.9848 360.795 45.4091C363.492 50.8333 364.841 57.1667 364.841 64.4091C364.841 71.6212 363.492 77.9242 360.795 83.3182C358.098 88.7121 354.311 92.9091 349.432 95.9091C344.553 98.9091 338.841 100.409 332.295 100.409ZM332.341 89C336.583 89 340.098 87.8788 342.886 85.6364C345.674 83.3939 347.735 80.4091 349.068 76.6818C350.432 72.9545 351.114 68.8485 351.114 64.3636C351.114 59.9091 350.432 55.8182 349.068 52.0909C347.735 48.3333 345.674 45.3182 342.886 43.0455C340.098 40.7727 336.583 39.6364 332.341 39.6364C328.068 39.6364 324.523 40.7727 321.705 43.0455C318.917 45.3182 316.841 48.3333 315.477 52.0909C314.144 55.8182 313.477 59.9091 313.477 64.3636C313.477 68.8485 314.144 72.9545 315.477 76.6818C316.841 80.4091 318.917 83.3939 321.705 85.6364C324.523 87.8788 328.068 89 332.341 89ZM393.023 99L372.477 29.1818H386.523L400.205 80.4545H400.886L414.614 29.1818H428.659L442.295 80.2273H442.977L456.568 29.1818H470.614L450.114 99H436.25L422.068 48.5909H421.023L406.841 99H393.023ZM516.102 29.1818V40.0909H477.966V29.1818H516.102ZM488.193 12.4545H501.784V78.5C501.784 81.1364 502.178 83.1212 502.966 84.4545C503.754 85.7576 504.769 86.6515 506.011 87.1364C507.284 87.5909 508.663 87.8182 510.148 87.8182C511.239 87.8182 512.193 87.7424 513.011 87.5909C513.83 87.4394 514.466 87.3182 514.92 87.2273L517.375 98.4545C516.587 98.7576 515.466 99.0606 514.011 99.3636C512.557 99.697 510.739 99.8788 508.557 99.9091C504.981 99.9697 501.648 99.3333 498.557 98C495.466 96.6667 492.966 94.6061 491.057 91.8182C489.148 89.0303 488.193 85.5303 488.193 81.3182V12.4545ZM531.136 99V29.1818H544.727V99H531.136ZM538 18.4091C535.636 18.4091 533.606 17.6212 531.909 16.0454C530.242 14.4394 529.409 12.5303 529.409 10.3182C529.409 8.07575 530.242 6.16666 531.909 4.5909C533.606 2.98485 535.636 2.18182 538 2.18182C540.364 2.18182 542.379 2.98485 544.045 4.5909C545.742 6.16666 546.591 8.07575 546.591 10.3182C546.591 12.5303 545.742 14.4394 544.045 16.0454C542.379 17.6212 540.364 18.4091 538 18.4091ZM593.102 100.409C586.223 100.409 580.299 98.9394 575.33 96C570.39 93.0303 566.572 88.8636 563.875 83.5C561.208 78.1061 559.875 71.7879 559.875 64.5455C559.875 57.3939 561.208 51.0909 563.875 45.6364C566.572 40.1818 570.33 35.9242 575.148 32.8636C579.996 29.803 585.663 28.2727 592.148 28.2727C596.087 28.2727 599.905 28.9242 603.602 30.2273C607.299 31.5303 610.617 33.5758 613.557 36.3636C616.496 39.1515 618.814 42.7727 620.511 47.2273C622.208 51.6515 623.057 57.0303 623.057 63.3636V68.1818H567.557V58H609.739C609.739 54.4242 609.011 51.2576 607.557 48.5C606.102 45.7121 604.057 43.5151 601.42 41.9091C598.814 40.303 595.754 39.5 592.239 39.5C588.42 39.5 585.087 40.4394 582.239 42.3182C579.42 44.1667 577.239 46.5909 575.693 49.5909C574.178 52.5606 573.42 55.7879 573.42 59.2727V67.2273C573.42 71.8939 574.239 75.8636 575.875 79.1364C577.542 82.4091 579.86 84.9091 582.83 86.6364C585.799 88.3333 589.269 89.1818 593.239 89.1818C595.814 89.1818 598.163 88.8182 600.284 88.0909C602.405 87.3333 604.239 86.2121 605.784 84.7273C607.33 83.2424 608.511 81.4091 609.33 79.2273L622.193 81.5455C621.163 85.3333 619.314 88.6515 616.648 91.5C614.011 94.3182 610.693 96.5152 606.693 98.0909C602.723 99.6364 598.193 100.409 593.102 100.409Z\" fill=\"white\" />\n            <!-- RISK (red) -->\n            <path d=\"M221.045 205V111.909H259.5C266.47 111.909 272.485 113.167 277.545 115.682C282.636 118.167 286.561 121.742 289.318 126.409C292.076 131.045 293.455 136.545 293.455 142.909C293.455 149.364 292.045 154.848 289.227 159.364C286.409 163.848 282.409 167.273 277.227 169.636C272.045 171.97 265.909 173.136 258.818 173.136H234.5V155.409H254.636C258.03 155.409 260.864 154.97 263.136 154.091C265.439 153.182 267.182 151.818 268.364 150C269.545 148.152 270.136 145.788 270.136 142.909C270.136 140.03 269.545 137.652 268.364 135.773C267.182 133.864 265.439 132.439 263.136 131.5C260.833 130.53 258 130.045 254.636 130.045H243.545V205H221.045ZM273.455 162.455L296.636 205H272.091L249.364 162.455H273.455ZM328.295 111.909V205H305.795V111.909H328.295ZM392.739 139.818C392.436 136.485 391.087 133.894 388.693 132.045C386.33 130.167 382.951 129.227 378.557 129.227C375.648 129.227 373.223 129.606 371.284 130.364C369.345 131.121 367.89 132.167 366.92 133.5C365.951 134.803 365.451 136.303 365.42 138C365.36 139.394 365.633 140.621 366.239 141.682C366.875 142.742 367.784 143.682 368.966 144.5C370.178 145.288 371.633 145.985 373.33 146.591C375.027 147.197 376.936 147.727 379.057 148.182L387.057 150C391.663 151 395.723 152.333 399.239 154C402.784 155.667 405.754 157.652 408.148 159.955C410.572 162.258 412.405 164.909 413.648 167.909C414.89 170.909 415.527 174.273 415.557 178C415.527 183.879 414.042 188.924 411.102 193.136C408.163 197.348 403.936 200.576 398.42 202.818C392.936 205.061 386.314 206.182 378.557 206.182C370.769 206.182 363.981 205.015 358.193 202.682C352.405 200.348 347.905 196.803 344.693 192.045C341.481 187.288 339.83 181.273 339.739 174H361.284C361.466 177 362.269 179.5 363.693 181.5C365.117 183.5 367.072 185.015 369.557 186.045C372.072 187.076 374.981 187.591 378.284 187.591C381.314 187.591 383.89 187.182 386.011 186.364C388.163 185.545 389.814 184.409 390.966 182.955C392.117 181.5 392.708 179.833 392.739 177.955C392.708 176.197 392.163 174.697 391.102 173.455C390.042 172.182 388.405 171.091 386.193 170.182C384.011 169.242 381.223 168.379 377.83 167.591L368.102 165.318C360.042 163.47 353.693 160.485 349.057 156.364C344.42 152.212 342.117 146.606 342.148 139.545C342.117 133.788 343.663 128.742 346.784 124.409C349.905 120.076 354.223 116.697 359.739 114.273C365.254 111.848 371.542 110.636 378.602 110.636C385.814 110.636 392.072 111.864 397.375 114.318C402.708 116.742 406.845 120.152 409.784 124.545C412.723 128.939 414.223 134.03 414.284 139.818H392.739ZM426.92 205V111.909H449.42V151.273H450.648L481.42 111.909H507.92L474.92 153.455L508.511 205H481.602L458.42 168.409L449.42 179.773V205H426.92Z\" fill=\"#e00000\" />\n          </svg>\n        </div>\n\n        <!-- Diagram-Only Navigation Controls (Visible in Canvas view, hidden in Documents drive) -->\n        <div class=\"diagram-nav-controls\" id=\"diagramNavControls\" style=\"display: none;\">\n          <!-- All changes saved next to Bowtie Risk logo (fixed width to prevent shifting) -->\n          <div class=\"save-status\" id=\"saveStatus\">\n            <span class=\"status-dot saved\"></span>\n            <span class=\"status-text\">All changes saved</span>\n          </div>\n          <span id=\"navReadOnlyBadge\" class=\"badge-read-only\" style=\"display: none;\" title=\"You have view-only access to this diagram\">\n            <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>\n            <span>View Only</span>\n          </span>\n\n          <!-- Fixed Space -->\n          <div class=\"nav-control-spacer\"></div>\n\n          <!-- 1. Undo / Redo Controls (in tactile buttons) -->\n          <div class=\"undo-redo-controls\">\n            <button id=\"btnHeaderUndo\" class=\"btn btn-nav btn-undo-redo\" title=\"Undo (Ctrl+Z / Cmd+Z)\" disabled>\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 7v6h6\"/><path d=\"M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13\"/></svg>\n            </button>\n            <button id=\"btnHeaderRedo\" class=\"btn btn-nav btn-undo-redo\" title=\"Redo (Ctrl+Y / Cmd+Shift+Z)\" disabled>\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 7v6h-6\"/><path d=\"M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13\"/></svg>\n            </button>\n          </div>\n\n          <!-- Mobile Burger Menu Button (Visible only on mobile <= 900px in Canvas view) -->\n          <button id=\"btnMobileBurgerMenu\" class=\"btn btn-nav btn-mobile-burger\" title=\"Diagram Tools & Menu\">\n            <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\">\n              <line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\"/>\n              <line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\"/>\n              <line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\"/>\n            </svg>\n          </button>\n\n          <!-- 2. History Dropdown Wrapper -->\n          <div class=\"history-dropdown-wrapper\" style=\"position: relative; display: inline-flex;\">\n            <button id=\"btnOpenHistory\" class=\"btn btn-nav btnDiagramHistory\" data-action=\"btnDiagramHistory\" title=\"Diagram Change History & Revert\">\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 14 14\"/></svg>\n              <span id=\"historyCountBadge\" class=\"badge\" style=\"display: none;\">0</span>\n            </button>\n            <div id=\"diagramHistoryBackdrop\" class=\"diagram-history-backdrop\" style=\"display: none;\"></div>\n            <div id=\"diagramHistoryDropdown\" class=\"diagram-history-dropdown\" style=\"display: none;\">\n              <div class=\"diagram-history-header\">\n                <div class=\"history-header-title\">\n                  <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 14 14\"/></svg>\n                  <span>Diagram Change History</span>\n                </div>\n                <button type=\"button\" id=\"btnCloseHistoryDropdown\" class=\"btn-close-subtle\" title=\"Close history\">✕</button>\n              </div>\n              <div id=\"diagramHistoryList\" class=\"diagram-history-list\"></div>\n            </div>\n          </div>\n\n          <!-- 3. Zoom Controls -->\n          <div class=\"btn-group zoom-controls\">\n            <button id=\"btnZoomOut\" class=\"btn btn-icon\" title=\"Zoom Out (Ctrl -)\">\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/><line x1=\"8\" y1=\"11\" x2=\"14\" y2=\"11\"/></svg>\n            </button>\n            <span id=\"zoomDisplay\" class=\"zoom-indicator\" title=\"Current Zoom Level\">100%</span>\n            <button id=\"btnZoomIn\" class=\"btn btn-icon\" title=\"Zoom In (Ctrl +)\">\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/><line x1=\"11\" y1=\"8\" x2=\"11\" y2=\"14\"/><line x1=\"8\" y1=\"11\" x2=\"14\" y2=\"11\"/></svg>\n            </button>\n            <button id=\"btnZoomFit\" class=\"btn btn-icon\" title=\"Fit All Diagrams (Zoom to Fit)\">\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7\"/></svg>\n            </button>\n            <button id=\"btnZoomReset\" class=\"btn btn-icon\" title=\"Reset View (1:1)\">\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/></svg>\n            </button>\n            <button id=\"btnToggleAutoZoom\" class=\"btn btn-icon\" title=\"Toggle Auto Zoom on Element Selection\">\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/><circle cx=\"11\" cy=\"11\" r=\"3.5\"/></svg>\n            </button>\n          </div>\n\n          <!-- 4. Line Up Elements & Auto Lineup Toggle -->\n          <button id=\"btnAutoBalance\" class=\"btn btn-nav\" title=\"Line Up Elements - Align and auto-balance diagram layout\">\n            <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z\"/><polyline points=\"3.27 6.96 12 12.01 20.73 6.96\"/><line x1=\"12\" y1=\"22.08\" x2=\"12\" y2=\"12\"/></svg>\n          </button>\n          <button id=\"btnToggleAutoAlign\" class=\"btn btn-nav\" title=\"Auto Line Up: Off (Click to toggle auto-align on layout change)\">\n            <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"21 8 21 21 3 21 3 8\"/><line x1=\"1\" y1=\"3\" x2=\"23\" y2=\"3\"/><line x1=\"10\" y1=\"12\" x2=\"14\" y2=\"12\"/></svg>\n          </button>\n\n          <!-- 5. Order Barriers (Sand Timer Icon) -->\n          <button id=\"btnOrderBarriersTime\" class=\"btn btn-nav\" title=\"Order Barriers - Sort branch barriers by response time\">\n            <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 22h14\"/><path d=\"M5 2h14\"/><path d=\"M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22\"/><path d=\"M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2\"/></svg>\n          </button>\n\n          <!-- 6. Cost Analysis -->\n          <button id=\"btnOpenCostAnalysis\" class=\"btn btn-nav\" title=\"Cost Analysis - Barrier expenditure vs protection level\">\n            <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"23\"/><path d=\"M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6\"/></svg>\n          </button>\n\n          <!-- 7. Situation Runner -->\n          <button id=\"btnToggleSituationRunner\" class=\"btn btn-nav btn-nav-sim\" title=\"Situation Runner - Incident scenario simulation & analysis\">\n            <span class=\"sim-status-dot\"></span>\n            <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polygon points=\"5 3 19 12 5 21 5 3\"/></svg>\n          </button>\n\n          <!-- 8. Share Button -->\n          <button id=\"btnOpenShare\" class=\"btn btn-nav\" title=\"Share Diagram - Invite collaborators & manage permissions\">\n            <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"/><polyline points=\"16 6 12 2 8 6\"/><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"15\"/></svg>\n          </button>\n\n          <!-- 9. Export Dropdown -->\n          <div class=\"dropdown\">\n            <button id=\"btnExportMenu\" class=\"btn btn-nav\" title=\"Export Diagram - Save JSON, PNG, or PDF report\">\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>\n            </button>\n            <div id=\"exportDropdown\" class=\"dropdown-menu\">\n              <button id=\"btnExportJson\" class=\"dropdown-item\">\n                <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>\n                Save File (.bowtie.json)\n              </button>\n              <button id=\"btnExportPng\" class=\"dropdown-item\">\n                <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>\n                Export as PNG Image\n              </button>\n              <button id=\"btnExportPdf\" class=\"dropdown-item\">\n                <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"/></svg>\n                Export PDF Risk Report\n              </button>\n              <hr class=\"dropdown-divider\">\n              <button id=\"btnImportJson\" class=\"dropdown-item\">\n                <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/></svg>\n                Import Save File\n              </button>\n              <input type=\"file\" id=\"fileInput\" accept=\".json\" style=\"display: none;\">\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Right Nav: Presence & User Profile -->\n      <div class=\"nav-right\">\n        <!-- Live Collaborators (Google Docs Style) -->\n        <div class=\"collaborators-stack\" id=\"collaboratorsStack\" title=\"Users currently viewing/editing this diagram\">\n          <span class=\"collab-pulse-dot\" title=\"Real-time collaboration active\"></span>\n          <div class=\"collab-avatars-list\" id=\"collabAvatarsList\">\n            <!-- Populated via Real-time SSE -->\n          </div>\n        </div>\n\n        <!-- User Profile / Account Dropdown (Avatar Icon Only) -->\n        <div class=\"dropdown user-profile-wrapper\">\n          <button id=\"btnUserProfile\" class=\"btn btn-profile-avatar-only\" title=\"Account Details & Profile\">\n            <div class=\"avatar\" id=\"userAvatar\">DJ</div>\n          </button>\n          <span id=\"userEmailDisplay\" class=\"user-email-label\" style=\"display: none;\">djb@bowtierisk.com</span>\n          <div id=\"userProfileDropdown\" class=\"dropdown-menu user-profile-dropdown\">\n            <div class=\"user-profile-header\">\n              <div class=\"avatar-lg\" id=\"dropdownAvatar\">DJ</div>\n              <div class=\"user-profile-details\">\n                <div class=\"user-profile-name\" id=\"dropdownUserName\">David J. Bowtie</div>\n                <div class=\"user-profile-email\" id=\"dropdownUserEmail\">djb@bowtierisk.com</div>\n                <div class=\"user-profile-meta\">\n                  <span class=\"badge\" id=\"dropdownUserOrg\">Offshore Operations</span>\n                  <span class=\"badge\" id=\"dropdownUserRole\">Risk Assessor</span>\n                  <span class=\"badge badge-admin-chip\" id=\"dropdownUserAdminBadge\" style=\"display: none;\">Administrator</span>\n                </div>\n              </div>\n            </div>\n            <hr class=\"dropdown-divider\">\n            <button id=\"btnOpenMyProfile\" class=\"dropdown-item\" style=\"color: #0f172a; font-weight: 500;\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></svg>\n              My Profile & Avatar\n            </button>\n            <button id=\"btnOpenSettings\" class=\"dropdown-item\" style=\"color: #0f172a; font-weight: 500;\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>\n              Custom Settings\n            </button>\n            <button id=\"btnOpenAdminPortal\" class=\"dropdown-item\" style=\"display: none; color: #7c3aed; font-weight: 500;\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 14h-2v-2h2zm0-4h-2V7h2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>\n              Admin Portal\n            </button>\n            <button id=\"btnSignOut\" class=\"dropdown-item\" style=\"color: #ef4444;\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/><polyline points=\"16 17 21 12 16 7\"/><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"/></svg>\n              Sign Out\n            </button>\n          </div>\n        </div>\n      </div>\n    </header>\n\n    <!-- Document Tabs Bar (Desktop multi-tab document switcher, Chrome-style) -->\n    <nav class=\"diagram-tabs-bar\" id=\"diagramTabsBar\">\n      <div class=\"tabs-scroll-container\" id=\"tabsScrollContainer\">\n        <!-- Dynamically rendered tabs (Documents tab + open diagram tabs + inline add button) -->\n      </div>\n    </nav>\n\n    <!-- On-Premise Unlicensed Read-Only Mode Banner -->\n    <div id=\"unlicensedBanner\" class=\"unlicensed-banner\" style=\"display: none;\">\n      <div class=\"unlicensed-banner-left\">\n        <span class=\"unlicensed-badge\">UNLICENSED READ-ONLY MODE</span>\n        <span id=\"unlicensedBannerMessage\">This self-hosted instance does not have an active license key. Creating or editing Bowtie diagrams is disabled until a license is activated.</span>\n      </div>\n      <div class=\"unlicensed-banner-actions\">\n        <a href=\"https://bowtierisk.com/on-premise\" target=\"_blank\" rel=\"noopener\" class=\"btn btn-xs unlicensed-btn-portal\">Get Free License Key ↗</a>\n        <button type=\"button\" id=\"btnBannerActivateLicense\" class=\"btn btn-xs unlicensed-btn-activate\">Activate License in Admin Console</button>\n      </div>\n    </div>\n\n    <!-- Google Drive Style Documents Landing Page -->\n    <div id=\"documentsLandingView\" class=\"documents-landing-view\" style=\"display: none;\">\n      <!-- Drive Sidebar -->\n      <aside class=\"drive-sidebar\">\n        <div class=\"drive-nav-list\">\n          <button class=\"drive-nav-item active\" data-section=\"all\">\n            <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/></svg>\n            <span>My Files</span>\n          </button>\n          <button class=\"drive-nav-item\" data-section=\"shared\">\n            <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/></svg>\n            <span>Shared With Me</span>\n          </button>\n          <button class=\"drive-nav-item\" data-section=\"recent\">\n            <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 14 14\"/></svg>\n            <span>Recent Activity</span>\n          </button>\n        </div>\n\n        <div class=\"drive-storage-stats\">\n          <div class=\"stats-label\">Cloud Workspace</div>\n          <div class=\"stats-row\">\n            <span id=\"driveStatsDiagCount\">0 Diagrams</span>\n            <span id=\"driveStatsFolderCount\">0 Folders</span>\n          </div>\n        </div>\n\n        <div class=\"drive-app-version\" id=\"driveAppVersion\" title=\"Bowtie Risk System Version • Click to check status\">\n          <span class=\"version-dot\"></span>\n          <span class=\"version-text\" id=\"appVersionString\">Version 1.1.1111</span>\n        </div>\n      </aside>\n\n      <!-- Drive Main Content Area -->\n      <main class=\"drive-main-content\">\n        <!-- Drive Toolbar (Search bar & grid toggle above, actions & breadcrumbs below) -->\n        <div class=\"drive-toolbar\">\n          <!-- Row 1: Search & Grid/List View Toggle (Centered) -->\n          <div class=\"drive-toolbar-search-row\">\n            <div class=\"drive-search-box\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/></svg>\n              <input type=\"text\" id=\"driveSearchInput\" placeholder=\"Search diagrams and folders...\">\n            </div>\n\n            <button id=\"btnDriveToggleView\" class=\"btn btn-icon btn-outline\" title=\"Switch between Grid and List view\">\n              <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/></svg>\n            </button>\n          </div>\n\n          <!-- Row 2: Action Buttons (Centered) -->\n          <div class=\"drive-toolbar-actions-row\">\n            <button id=\"btnDriveNewDiagram\" class=\"btn btn-primary\" title=\"Create a new Bowtie diagram\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"/><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/></svg>\n              <span>New Bowtie</span>\n            </button>\n            <button id=\"btnDriveNewFolder\" class=\"btn btn-outline\" title=\"Create a new folder in current location\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/><line x1=\"12\" y1=\"11\" x2=\"12\" y2=\"17\"/><line x1=\"9\" y1=\"14\" x2=\"15\" y2=\"14\"/></svg>\n              <span>+ Folder</span>\n            </button>\n          </div>\n\n          <!-- Row 3: Location Breadcrumb (Left-aligned) -->\n          <div class=\"drive-toolbar-breadcrumb-row\">\n            <div class=\"drive-breadcrumbs\" id=\"driveBreadcrumbs\">\n              <!-- Dynamic breadcrumbs: Cloud Drive left-aligned below action buttons -->\n            </div>\n          </div>\n        </div>\n\n        <!-- Folders Section -->\n        <section class=\"drive-section\" id=\"driveFoldersSection\">\n          <h3 class=\"drive-section-title\">Folders</h3>\n          <div class=\"drive-folders-grid\" id=\"driveFoldersGrid\">\n            <!-- Dynamic Folder Cards -->\n          </div>\n        </section>\n\n        <!-- Diagrams Section -->\n        <section class=\"drive-section\" id=\"driveDiagramsSection\">\n          <h3 class=\"drive-section-title\">Bowtie Risk Assessments</h3>\n          <!-- Grid View -->\n          <div class=\"drive-diagrams-grid\" id=\"driveDiagramsGrid\">\n            <!-- Dynamic Diagram Cards -->\n          </div>\n          <!-- List View -->\n          <div class=\"drive-diagrams-list\" id=\"driveDiagramsList\" style=\"display: none;\">\n            <table class=\"drive-table\">\n              <thead>\n                <tr>\n                  <th>Diagram Title</th>\n                  <th>Folder</th>\n                  <th>Author</th>\n                  <th>Nodes</th>\n                  <th>Last Modified</th>\n                  <th>Actions</th>\n                </tr>\n              </thead>\n              <tbody id=\"driveDiagramsTableBody\">\n                <!-- Dynamic rows -->\n              </tbody>\n            </table>\n          </div>\n        </section>\n\n        <!-- Mobile Page Bottom Version -->\n        <div class=\"drive-mobile-version\" id=\"driveMobileVersion\" title=\"Bowtie Risk System Version • Click to check status\">\n          <span class=\"version-dot\"></span>\n          <span class=\"version-text\" id=\"appMobileVersionString\">Version 1.1.1111</span>\n        </div>\n      </main>\n    </div>\n\n    <!-- Main Workspace Layout (3-Pane: Left Tree, Center Canvas, Right Inspector) -->\n    <div class=\"workspace-layout\" id=\"canvasWorkspaceView\">\n      <!-- Mobile Drawer Backdrop Overlay -->\n      <div id=\"mobileDrawerBackdrop\" class=\"mobile-drawer-backdrop\"></div>\n\n      <!-- Mobile Diagram Menu Drawer Panel -->\n      <div id=\"mobileMenuPanel\" class=\"mobile-menu-drawer\" style=\"display: none;\">\n        <div class=\"mobile-menu-backdrop\" id=\"mobileMenuBackdrop\"></div>\n        <div class=\"mobile-menu-content\">\n          <div class=\"mobile-menu-header\">\n            <div class=\"mobile-menu-title\">\n              <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\"/></svg>\n              <span>Diagram Menu</span>\n            </div>\n            <button type=\"button\" class=\"mobile-menu-close-btn\" id=\"btnCloseMobileMenu\" aria-label=\"Close Diagram Menu\" title=\"Close Diagram Menu\">\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n                <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n              </svg>\n            </button>\n          </div>\n\n          <!-- Save Status in Panel -->\n          <div class=\"mobile-menu-status\">\n            <span class=\"status-dot saved\"></span>\n            <span id=\"mobileMenuStatusText\">All changes saved</span>\n          </div>\n\n          <div class=\"mobile-menu-body\">\n            <!-- Collaboration & Sharing -->\n            <div class=\"mobile-menu-section-label\">Collaboration & Security</div>\n            <button type=\"button\" class=\"mobile-menu-item\" id=\"mobileBtnShare\">\n              <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2563eb\" stroke-width=\"2\"><path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"/><polyline points=\"16 6 12 2 8 6\"/><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"15\"/></svg>\n              <div class=\"mobile-menu-item-text\">\n                <span class=\"primary\">Share Diagram</span>\n                <span class=\"secondary\">Manage access, team roles & link</span>\n              </div>\n            </button>\n\n            <button type=\"button\" class=\"mobile-menu-item\" id=\"mobileBtnHistory\">\n              <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#64748b\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 14 14\"/></svg>\n              <div class=\"mobile-menu-item-text\">\n                <span class=\"primary\">Change History & Revert</span>\n                <span class=\"secondary\">View revision snapshots & rollback</span>\n              </div>\n            </button>\n\n            <hr class=\"mobile-menu-divider\">\n\n            <!-- Assessment Tools -->\n            <div class=\"mobile-menu-section-label\">Assessment Tools</div>\n            <button type=\"button\" class=\"mobile-menu-item\" id=\"mobileBtnSituationRunner\">\n              <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2\"><polygon points=\"5 3 19 12 5 21 5 3\"/></svg>\n              <div class=\"mobile-menu-item-text\">\n                <span class=\"primary\">Situation Runner</span>\n                <span class=\"secondary\">Incident simulation & scenario testing</span>\n              </div>\n            </button>\n\n            <button type=\"button\" class=\"mobile-menu-item\" id=\"mobileBtnOrderBarriers\">\n              <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#d97706\" stroke-width=\"2\"><path d=\"M5 22h14\"/><path d=\"M5 2h14\"/><path d=\"M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22\"/><path d=\"M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2\"/></svg>\n              <div class=\"mobile-menu-item-text\">\n                <span class=\"primary\">Order Barriers</span>\n                <span class=\"secondary\">Sort branch barriers by timing</span>\n              </div>\n            </button>\n\n            <button type=\"button\" class=\"mobile-menu-item\" id=\"mobileBtnAutoBalance\">\n              <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#0284c7\" stroke-width=\"2\"><path d=\"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z\"/><polyline points=\"3.27 6.96 12 12.01 20.73 6.96\"/><line x1=\"12\" y1=\"22.08\" x2=\"12\" y2=\"12\"/></svg>\n              <div class=\"mobile-menu-item-text\">\n                <span class=\"primary\">Line Up Elements</span>\n                <span class=\"secondary\">Auto-align nodes & symmetrical balance</span>\n              </div>\n            </button>\n\n            <button type=\"button\" class=\"mobile-menu-item\" id=\"mobileBtnToggleAutoAlign\">\n              <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2563eb\" stroke-width=\"2\"><polyline points=\"21 8 21 21 3 21 3 8\"/><line x1=\"1\" y1=\"3\" x2=\"23\" y2=\"3\"/><line x1=\"10\" y1=\"12\" x2=\"14\" y2=\"12\"/></svg>\n              <div class=\"mobile-menu-item-text\">\n                <span class=\"primary\" id=\"mobileAutoAlignText\">Auto Align: OFF</span>\n                <span class=\"secondary\">Automatically align elements on any layout change</span>\n              </div>\n            </button>\n\n            <button type=\"button\" class=\"mobile-menu-item\" id=\"mobileBtnCostAnalysis\">\n              <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#10b981\" stroke-width=\"2\"><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"23\"/><path d=\"M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6\"/></svg>\n              <div class=\"mobile-menu-item-text\">\n                <span class=\"primary\">Cost Analysis</span>\n                <span class=\"secondary\">Barrier expenditure vs protection level</span>\n              </div>\n            </button>\n\n            <hr class=\"mobile-menu-divider\">\n\n            <!-- Zoom & Canvas Controls -->\n            <div class=\"mobile-menu-section-label\">Canvas View & Zoom</div>\n            <div class=\"mobile-zoom-row\">\n              <button type=\"button\" class=\"btn btn-outline btn-sm mobile-zoom-btn\" id=\"mobileBtnZoomOut\" title=\"Zoom Out\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/><line x1=\"8\" y1=\"11\" x2=\"14\" y2=\"11\"/></svg>\n                <span>Out</span>\n              </button>\n              <span class=\"mobile-zoom-indicator\" id=\"mobileZoomDisplay\">100%</span>\n              <button type=\"button\" class=\"btn btn-outline btn-sm mobile-zoom-btn\" id=\"mobileBtnZoomIn\" title=\"Zoom In\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/><line x1=\"11\" y1=\"8\" x2=\"11\" y2=\"14\"/><line x1=\"8\" y1=\"11\" x2=\"14\" y2=\"11\"/></svg>\n                <span>In</span>\n              </button>\n              <button type=\"button\" class=\"btn btn-outline btn-sm mobile-zoom-btn\" id=\"mobileBtnZoomFit\" title=\"Fit to Screen\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7\"/></svg>\n                <span>Fit</span>\n              </button>\n              <button type=\"button\" class=\"btn btn-outline btn-sm mobile-zoom-btn\" id=\"mobileBtnZoomReset\" title=\"Reset View (1:1)\">\n                <span>1:1</span>\n              </button>\n            </div>\n            <button type=\"button\" class=\"mobile-menu-item\" id=\"mobileBtnToggleAutoZoom\" style=\"margin-top: 6px;\">\n              <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2563eb\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/><circle cx=\"11\" cy=\"11\" r=\"3.5\"/></svg>\n              <div class=\"mobile-menu-item-text\">\n                <span class=\"primary\" id=\"mobileAutoZoomText\">Auto Zoom: OFF</span>\n                <span class=\"secondary\">Zoom in on element selection, fit on deselect</span>\n              </div>\n            </button>\n\n            <hr class=\"mobile-menu-divider\">\n\n            <!-- Export & Save Files -->\n            <div class=\"mobile-menu-section-label\">Export & Files</div>\n            <div class=\"mobile-export-grid\">\n              <button type=\"button\" class=\"btn btn-outline btn-sm mobile-action-chip\" id=\"mobileBtnExportJson\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>\n                <span>Save JSON</span>\n              </button>\n              <button type=\"button\" class=\"btn btn-outline btn-sm mobile-action-chip\" id=\"mobileBtnExportPng\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>\n                <span>Export PNG</span>\n              </button>\n              <button type=\"button\" class=\"btn btn-outline btn-sm mobile-action-chip\" id=\"mobileBtnExportPdf\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"/></svg>\n                <span>Export PDF</span>\n              </button>\n              <button type=\"button\" class=\"btn btn-outline btn-sm mobile-action-chip\" id=\"mobileBtnImportJson\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/></svg>\n                <span>Import JSON</span>\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Left Sidebar Edge Restore Handle (when collapsed) -->\n      <button id=\"btnEdgeToggleLeft\" class=\"sidebar-edge-tab edge-left\" title=\"Show Diagram Tree & Library\" style=\"display: none;\">\n        <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><polyline points=\"9 18 15 12 9 6\"></polyline></svg>\n      </button>\n\n      <!-- Right Sidebar Edge Restore Handle (when collapsed) -->\n      <button id=\"btnEdgeToggleRight\" class=\"sidebar-edge-tab edge-right\" title=\"Show Element Inspector\" style=\"display: none;\">\n        <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><polyline points=\"15 18 9 12 15 6\"></polyline></svg>\n      </button>\n\n      <!-- Left-hand Pane: Hierarchical Tree & Global Library -->\n      <aside class=\"sidebar-pane left-pane\" id=\"leftPane\">\n        <div class=\"pane-header\">\n          <div class=\"pane-tabs\">\n            <button class=\"pane-tab active\" data-tab=\"treeTab\" id=\"tabTreeBtn\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/></svg>\n              <span>Diagram Tree</span>\n            </button>\n            <button class=\"pane-tab\" data-tab=\"libraryTab\" id=\"tabLibraryBtn\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 19.5A2.5 2.5 0 0 1 6.5 17H20\"/><path d=\"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z\"/></svg>\n              <span>Element Library</span>\n            </button>\n          </div>\n          <button id=\"btnCollapseLeft\" class=\"btn-toggle-pane\" title=\"Collapse left panel\">‹</button>\n        </div>\n\n        <!-- Tree View Tab -->\n        <div class=\"pane-body tab-content active\" id=\"treeTab\">\n          <div class=\"tree-search-bar\">\n            <input type=\"text\" id=\"treeSearchInput\" placeholder=\"Filter elements...\">\n          </div>\n          <div class=\"tree-container\" id=\"treeContainer\">\n            <!-- Dynamically populated hierarchical tree -->\n          </div>\n        </div>\n\n        <!-- Global Elements Library Tab -->\n        <div class=\"pane-body tab-content\" id=\"libraryTab\">\n          <div class=\"library-header-actions\">\n            <span class=\"text-muted text-xs\">Reusable elements across diagrams</span>\n            <button id=\"btnNewGlobalElement\" class=\"btn btn-xs btn-primary\">+ New Master Element</button>\n          </div>\n          <div class=\"global-elements-list\" id=\"globalElementsList\">\n            <!-- Dynamically populated master elements -->\n          </div>\n        </div>\n      </aside>\n\n      <!-- Center Pane: Infinite Canvas -->\n      <main class=\"canvas-viewport\" id=\"canvasViewport\">\n        <!-- Canvas Toolbar Overlay -->\n        <div class=\"canvas-floating-toolbar\">\n          <div class=\"tool-group\">\n            <button id=\"toolAddThreat\" class=\"tool-btn\" title=\"Add Threat to active diagram\">\n              <span class=\"tool-color-dot\" style=\"background: #2563eb;\"></span>\n              Threat\n            </button>\n            <button id=\"toolAddOutcome\" class=\"tool-btn\" title=\"Add Outcome to active diagram\">\n              <span class=\"tool-color-dot\" style=\"background: #dc2626;\"></span>\n              Outcome\n            </button>\n            <button id=\"toolAddBarrier\" class=\"tool-btn\" title=\"Add Barrier to active branch\">\n              <span class=\"tool-color-dot\" style=\"background: #334155;\"></span>\n              Barrier\n            </button>\n            <button id=\"toolAddEscalation\" class=\"tool-btn\" title=\"Add Escalation Factor\">\n              <span class=\"tool-color-dot\" style=\"background: #eab308;\"></span>\n              Escalation Factor\n            </button>\n            <button id=\"toolAddMitigation\" class=\"tool-btn\" title=\"Add Mitigation Control\">\n              <span class=\"tool-color-dot\" style=\"background: #64748b;\"></span>\n              Mitigation\n            </button>\n          </div>\n        </div>\n\n        <!-- Situation Runner Floating Control HUD -->\n        <div class=\"situation-runner-hud collapsed closed\" id=\"situationRunnerHud\" style=\"display: none;\">\n          <div class=\"sim-hud-header\" id=\"simHudHeader\" title=\"Drag to move panel • Click to collapse/expand\">\n            <div class=\"sim-hud-title\">\n              <span class=\"sim-drag-grip\" title=\"Drag to move Situation Runner\">\n                <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\">\n                  <circle cx=\"8\" cy=\"6\" r=\"1.5\" fill=\"currentColor\"/>\n                  <circle cx=\"16\" cy=\"6\" r=\"1.5\" fill=\"currentColor\"/>\n                  <circle cx=\"8\" cy=\"12\" r=\"1.5\" fill=\"currentColor\"/>\n                  <circle cx=\"16\" cy=\"12\" r=\"1.5\" fill=\"currentColor\"/>\n                  <circle cx=\"8\" cy=\"18\" r=\"1.5\" fill=\"currentColor\"/>\n                  <circle cx=\"16\" cy=\"18\" r=\"1.5\" fill=\"currentColor\"/>\n                </svg>\n              </span>\n              <span class=\"sim-radar-icon\">⚡</span>\n              <span class=\"sim-hud-heading\">SITUATION RUNNER</span>\n              <span class=\"sim-mode-tag\" id=\"simModeTag\">Simulated (30s)</span>\n              <span class=\"sim-status-pill idle\" id=\"simStatusPill\">IDLE</span>\n            </div>\n            <div class=\"sim-hud-header-actions\">\n              <button type=\"button\" id=\"btnSimCollapse\" class=\"btn-sim-icon\" title=\"Collapse / Expand Situation Runner\">+</button>\n              <button type=\"button\" id=\"btnSimClose\" class=\"btn-sim-icon\" title=\"Close Situation Runner\">×</button>\n            </div>\n          </div>\n\n          <div class=\"sim-hud-body\" id=\"simHudBody\">\n            <!-- Row 1: Threat Selection -->\n            <div class=\"sim-row sim-row-threat\">\n              <label for=\"simThreatSelect\" class=\"sim-label\">Initiating Threat:</label>\n              <select id=\"simThreatSelect\" class=\"sim-select\">\n                <option value=\"\">-- Choose Threat to Trigger --</option>\n              </select>\n              <button type=\"button\" id=\"btnSimOrderBarriers\" class=\"sim-pill\" style=\"font-size: 11px; padding: 4px 8px; margin-left: 6px; white-space: nowrap;\" title=\"Order barriers on this threat branch by time (shortest on the left)\">⏱️ Order by Time</button>\n            </div>\n\n            <!-- Row 2: Mode & Timing Controls -->\n            <div class=\"sim-row sim-row-mode\">\n              <div class=\"sim-pill-group\">\n                <button type=\"button\" class=\"sim-pill active\" id=\"btnSimModeSimulated\" data-mode=\"simulated\" title=\"Simulated: Scales run-through to ~30s with proportional delays\">\n                  ⏱️ Simulated (30s)\n                </button>\n                <button type=\"button\" class=\"sim-pill\" id=\"btnSimModeRealtime\" data-mode=\"realtime\" title=\"Real-Time: Runs 1s = 1s with live wall-clock tracking\">\n                  🕒 Real-Time\n                </button>\n              </div>\n\n              <!-- Speed multipliers (for Real-time & fast review) -->\n              <div class=\"sim-speed-group\" id=\"simSpeedGroup\">\n                <span class=\"sim-sublabel\">Speed:</span>\n                <button type=\"button\" class=\"sim-speed-btn active\" data-speed=\"1\">1x</button>\n                <button type=\"button\" class=\"sim-speed-btn\" data-speed=\"2\">2x</button>\n                <button type=\"button\" class=\"sim-speed-btn\" data-speed=\"5\">5x</button>\n                <button type=\"button\" class=\"sim-speed-btn\" data-speed=\"10\">10x</button>\n              </div>\n            </div>\n\n            <!-- Row 3: Real-Time Threat Activation Schedule & Past Event Tracking -->\n            <div class=\"sim-row sim-row-schedule\" id=\"simScheduleRow\" style=\"display: none;\">\n              <div class=\"sim-schedule-container\">\n                <div class=\"sim-schedule-nav\">\n                  <span class=\"sim-sublabel\">Threat Initiation:</span>\n                  <div class=\"sim-pill-group\">\n                    <button type=\"button\" class=\"sim-sched-btn active\" id=\"btnSchedImmediate\" data-delay=\"0\">⚡ Right Now</button>\n                    <button type=\"button\" class=\"sim-sched-btn\" id=\"btnSchedPast\">🕒 In the Past (Elapsed)</button>\n                    <button type=\"button\" class=\"sim-sched-btn\" data-delay=\"5\">+5s Delay</button>\n                    <button type=\"button\" class=\"sim-sched-btn\" data-delay=\"10\">+10s Delay</button>\n                  </div>\n                </div>\n\n                <!-- Input row for: Activated X hours Y minutes ago -->\n                <div class=\"sim-past-elapsed-box\" id=\"simPastElapsedBox\" style=\"display: none;\">\n                  <span class=\"sim-sublabel-accent\">Threat activated:</span>\n                  <div class=\"sim-time-field\">\n                    <input type=\"number\" id=\"simElapsedHours\" class=\"sim-time-input\" min=\"0\" max=\"720\" value=\"0\" placeholder=\"0\">\n                    <span class=\"sim-unit\">hours</span>\n                  </div>\n                  <div class=\"sim-time-field\">\n                    <input type=\"number\" id=\"simElapsedMins\" class=\"sim-time-input\" min=\"0\" max=\"59\" value=\"0\" placeholder=\"0\">\n                    <span class=\"sim-unit\">mins ago</span>\n                  </div>\n                  <div class=\"sim-quick-elapsed-pills\">\n                    <button type=\"button\" class=\"sim-quick-pill\" data-h=\"0\" data-m=\"15\">15m</button>\n                    <button type=\"button\" class=\"sim-quick-pill\" data-h=\"0\" data-m=\"30\">30m</button>\n                    <button type=\"button\" class=\"sim-quick-pill\" data-h=\"1\" data-m=\"0\">1h</button>\n                    <button type=\"button\" class=\"sim-quick-pill\" data-h=\"2\" data-m=\"0\">2h</button>\n                  </div>\n                </div>\n              </div>\n            </div>\n\n            <!-- Row 4: Large Digital Clock & Dual Timeline -->\n            <div class=\"sim-clock-card\">\n              <div class=\"sim-clock-display\" id=\"simClockDisplay\">00:00.0</div>\n              <div class=\"sim-clock-details\">\n                <div class=\"sim-clock-metric\">\n                  <span class=\"metric-label\">Sim Time</span>\n                  <span class=\"metric-val\" id=\"simMetricSim\">0.0s / 30.0s</span>\n                </div>\n                <div class=\"sim-clock-metric\">\n                  <span class=\"metric-label\">Proportional Real</span>\n                  <span class=\"metric-val\" id=\"simMetricReal\">0.0s / 0.0s</span>\n                </div>\n                <div class=\"sim-clock-metric\">\n                  <span class=\"metric-label\">Current Stage</span>\n                  <span class=\"metric-val text-accent\" id=\"simMetricStage\">Ready</span>\n                </div>\n              </div>\n            </div>\n\n            <!-- Row 5: Dynamic Progress Bar -->\n            <div class=\"sim-progress-track\">\n              <div class=\"sim-progress-fill\" id=\"simProgressFill\" style=\"width: 0%;\"></div>\n            </div>\n\n            <!-- Row 6: Transport Buttons -->\n            <div class=\"sim-transport-row\">\n              <button type=\"button\" id=\"btnSimPlay\" class=\"sim-transport-btn btn-sim-play\">\n                <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><polygon points=\"5 3 19 12 5 21 5 3\"/></svg>\n                <span class=\"btn-text-full\">Run Situation</span>\n                <span class=\"btn-text-short\">Run</span>\n              </button>\n              <button type=\"button\" id=\"btnSimPause\" class=\"sim-transport-btn btn-sim-pause\" disabled>\n                <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"6\" y=\"4\" width=\"4\" height=\"16\"/><rect x=\"14\" y=\"4\" width=\"4\" height=\"16\"/></svg>\n                <span>Pause</span>\n              </button>\n              <button type=\"button\" id=\"btnSimStep\" class=\"sim-transport-btn btn-sim-step\" title=\"Advance to next barrier event\">\n                <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><polygon points=\"5 4 15 12 5 20 5 4\"/><line x1=\"19\" y1=\"5\" x2=\"19\" y2=\"19\" stroke=\"currentColor\" stroke-width=\"2\"/></svg>\n                <span>Step</span>\n              </button>\n              <button type=\"button\" id=\"btnSimReset\" class=\"sim-transport-btn btn-sim-reset\" title=\"Reset scenario\">\n                <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"4\" y=\"4\" width=\"16\" height=\"16\"/></svg>\n                <span>Reset</span>\n              </button>\n              <button type=\"button\" id=\"btnSimToggleLog\" class=\"sim-transport-btn btn-sim-log-toggle\" title=\"Toggle incident log\">\n                <span>📋 Log</span>\n                <span class=\"badge\" id=\"simLogCountBadge\">0</span>\n              </button>\n            </div>\n\n            <!-- Collapsible Situation Event Log Drawer -->\n            <div class=\"sim-log-drawer\" id=\"simLogDrawer\" style=\"display: none;\">\n              <div class=\"sim-log-header\">\n                <span>Incident Chronology & Event Log</span>\n                <div class=\"sim-log-actions\">\n                  <button type=\"button\" id=\"btnSimCopyLog\" class=\"btn btn-xs btn-outline\">Copy Log</button>\n                  <button type=\"button\" id=\"btnSimClearLog\" class=\"btn btn-xs btn-outline\">Clear</button>\n                </div>\n              </div>\n              <div class=\"sim-log-list\" id=\"simLogList\">\n                <div class=\"sim-log-empty\">No simulation events recorded yet. Click 'Run Situation' to begin.</div>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <!-- The Canvas Container (Pan & Zoom transform applied here) -->\n        <div class=\"canvas-stage\" id=\"canvasStage\">\n          <!-- SVG Background Dot Grid Layer (locked to canvas transform) -->\n          <svg class=\"grid-layer\" id=\"gridLayer\" xmlns=\"http://www.w3.org/2000/svg\">\n            <defs>\n              <pattern id=\"canvasDotGrid\" width=\"24\" height=\"24\" patternUnits=\"userSpaceOnUse\">\n                <circle cx=\"12\" cy=\"12\" r=\"1.1\" fill=\"#cbd5e1\" shape-rendering=\"geometricPrecision\" />\n              </pattern>\n            </defs>\n            <rect x=\"-50000\" y=\"-50000\" width=\"100000\" height=\"100000\" fill=\"url(#canvasDotGrid)\" pointer-events=\"none\" />\n          </svg>\n\n          <!-- SVG Connecting Lines Layer -->\n          <svg class=\"connections-layer\" id=\"connectionsLayer\" xmlns=\"http://www.w3.org/2000/svg\" style=\"overflow: visible;\">\n            <defs>\n              <filter id=\"shadow\" x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\">\n                <feDropShadow dx=\"0\" dy=\"2\" stdDeviation=\"3\" flood-opacity=\"0.1\" />\n              </filter>\n            </defs>\n            <g id=\"connectionsGroup\"></g>\n            <g id=\"simulationLayer\"></g>\n          </svg>\n\n          <!-- HTML Nodes Layer -->\n          <div class=\"nodes-layer\" id=\"nodesLayer\"></div>\n        </div>\n\n        <!-- Canvas Mini HUD Coordinates -->\n        <div class=\"canvas-hud\" id=\"canvasHud\" title=\"Tap to toggle Zoom on Element / Zoom to Fit\">\n          <div class=\"hud-row\"><span class=\"hud-label\">x</span> <span id=\"hudX\">0</span></div>\n          <div class=\"hud-row\"><span class=\"hud-label\">y</span> <span id=\"hudY\">0</span></div>\n          <div class=\"hud-row\">\n            <span class=\"hud-label hud-icon-label\" title=\"Zoom\">\n              <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n                <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n              </svg>\n            </span>\n            <span id=\"hudMag\">100%</span>\n          </div>\n          <div class=\"hud-auto-zoom-pill\" id=\"hudAutoZoomToggle\" title=\"Toggle Auto Zoom when selecting elements\">Auto: OFF</div>\n        </div>\n      </main>\n\n      <!-- Right-hand Pane: Active Element Inspector & Metadata -->\n      <aside class=\"sidebar-pane right-pane\" id=\"rightPane\">\n        <div class=\"pane-header\">\n          <div class=\"inspector-title-row\">\n            <span class=\"pane-title\">Element Inspector</span>\n            <span id=\"activeNodeTypeBadge\" class=\"node-type-badge\">Barrier</span>\n          </div>\n          <button id=\"btnCollapseRight\" class=\"btn-toggle-pane\" title=\"Collapse right panel\">›</button>\n        </div>\n\n        <div class=\"pane-body\" id=\"inspectorBody\">\n          <div class=\"empty-selection\" id=\"emptySelectionMsg\">\n            <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#94a3b8\" stroke-width=\"1.5\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4M12 8h.01\"/></svg>\n            <p>Select any node on the canvas or tree to inspect and edit its properties.</p>\n          </div>\n\n          <form id=\"inspectorForm\" class=\"inspector-form\" style=\"display: none;\" novalidate>\n            <!-- Element Title / Header -->\n            <div class=\"form-group\">\n              <label for=\"inspectTitle\">Item Category / Header</label>\n              <input type=\"text\" id=\"inspectTitle\" class=\"form-input\">\n            </div>\n\n            <!-- Description / Content -->\n            <div class=\"form-group\">\n              <label for=\"inspectDesc\">Description / Main Text</label>\n              <textarea id=\"inspectDesc\" class=\"form-textarea\" rows=\"3\" placeholder=\"Enter node description or equipment tag...\"></textarea>\n            </div>\n\n            <!-- Global Element Linkage & Instance Sync -->\n            <div class=\"global-link-section\" id=\"globalLinkSection\">\n              <div class=\"section-badge-header\">\n                <span class=\"sub-label\">Master Element Status</span>\n                <span id=\"globalLinkBadge\" class=\"status-pill\">Standalone</span>\n              </div>\n              <p class=\"text-xs text-muted\" id=\"globalLinkInfo\">\n                This item is an independent instance. You can link it to a reusable master element.\n              </p>\n              <div class=\"global-actions-row\">\n                <button type=\"button\" id=\"btnLinkGlobal\" class=\"btn btn-xs btn-outline\">Link to Master Element</button>\n                <button type=\"button\" id=\"btnSyncGlobal\" class=\"btn btn-xs btn-primary\" style=\"display: none;\">Sync All Instances</button>\n              </div>\n            </div>\n\n            <!-- Operational Metadata -->\n            <div class=\"form-group\">\n              <label for=\"inspectResponsible\">Responsible Role / Owner</label>\n              <input type=\"text\" id=\"inspectResponsible\" class=\"form-input\" placeholder=\"e.g. Fire Safety Lead, Operations\">\n            </div>\n\n            <div class=\"form-row\">\n              <div class=\"form-group col-half\">\n                <label for=\"inspectEffectiveness\">Effectiveness</label>\n                <select id=\"inspectEffectiveness\" class=\"form-select\">\n                  <option value=\"\">-- None --</option>\n                  <option value=\"High\">High (Certified)</option>\n                  <option value=\"Medium\">Medium</option>\n                  <option value=\"Low\">Low (Degraded)</option>\n                  <option value=\"Critical\">Safety Critical</option>\n                </select>\n              </div>\n\n              <div class=\"form-group col-half\">\n                <label for=\"inspectCriticality\">Criticality / SCE</label>\n                <input type=\"text\" id=\"inspectCriticality\" class=\"form-input\" placeholder=\"e.g. SCE-01, Tier 1\">\n              </div>\n            </div>\n\n            <!-- Situation Runner Dynamic Barrier Timing -->\n            <div class=\"simulation-timing-box\" id=\"simulationTimingBox\">\n              <div class=\"section-badge-header\">\n                <span class=\"sub-label\">Situation Runner Dynamics</span>\n                <span class=\"status-pill status-pill-sim\">Timeframes</span>\n              </div>\n              <p class=\"text-xs text-muted mb-2\">Configure barrier dynamics for real-time and simulated threat propagation.</p>\n\n              <div class=\"form-row\">\n                <div class=\"form-group col-half\">\n                  <label for=\"inspectTimeToBarrier\" title=\"Travel duration from threat/previous node along wire to this barrier\">Time to Barrier (s)</label>\n                  <input type=\"number\" id=\"inspectTimeToBarrier\" class=\"form-input\" min=\"0\" step=\"0.5\" placeholder=\"e.g. 5.0\">\n                </div>\n                <div class=\"form-group col-half\">\n                  <label for=\"inspectDelayToActivate\" title=\"How long from threat initiation until barrier is fully active & armed\">Delay to Activate (s)</label>\n                  <input type=\"number\" id=\"inspectDelayToActivate\" class=\"form-input\" min=\"0\" step=\"0.5\" placeholder=\"e.g. 2.0\">\n                </div>\n              </div>\n\n              <div class=\"form-group\">\n                <label for=\"inspectBarrierLife\" title=\"How long barrier can hold against threat before breach\">Barrier Life / Holding (s)</label>\n                <input type=\"number\" id=\"inspectBarrierLife\" class=\"form-input\" min=\"0\" step=\"1\" placeholder=\"e.g. 15.0 (blank = indefinite)\">\n              </div>\n\n              <button type=\"button\" id=\"btnInspectOrderBarriers\" class=\"btn btn-xs btn-outline w-full mb-2\" style=\"display: flex; align-items: center; justify-content: center; gap: 6px;\" title=\"Order barriers along this branch by time to barrier (shortest on the left)\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/></svg>\n                <span>Order Barriers by Time (Shortest on Left)</span>\n              </button>\n\n              <button type=\"button\" id=\"btnInspectSortMitigations\" class=\"btn btn-xs btn-outline w-full mb-2\" style=\"display: none; align-items: center; justify-content: center; gap: 6px;\" title=\"Align mitigations attached to this escalation factor horizontally\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 12h18\"/><path d=\"M7 8l-4 4 4 4\"/><path d=\"M17 8l4 4-4 4\"/></svg>\n                <span>Align Mitigations Horizontally</span>\n              </button>\n            </div>\n\n            <!-- Financial Costing & Protection Analysis Box -->\n            <div class=\"financial-costing-box\" id=\"financialCostingBox\">\n              <div class=\"section-badge-header\">\n                <span class=\"sub-label\">Financial & Protection Analysis</span>\n                <span class=\"status-pill status-pill-cost\" id=\"inspectCostStatusBadge\">Cost vs Protection</span>\n              </div>\n              <p class=\"text-xs text-muted mb-2\">Cost of implementation/maintenance vs protective risk reduction provided.</p>\n\n              <div class=\"form-row\">\n                <div class=\"form-group col-half\">\n                  <label for=\"inspectCostCurrency\">Currency</label>\n                  <select id=\"inspectCostCurrency\" class=\"form-select\">\n                    <option value=\"USD\">USD ($)</option>\n                    <option value=\"EUR\">EUR (€)</option>\n                    <option value=\"GBP\">GBP (£)</option>\n                    <option value=\"AUD\">AUD (A$)</option>\n                    <option value=\"CAD\">CAD (C$)</option>\n                    <option value=\"NOK\">NOK (kr)</option>\n                    <option value=\"SGD\">SGD (S$)</option>\n                  </select>\n                </div>\n                <div class=\"form-group col-half\">\n                  <label for=\"inspectCostEstimate\">Cost Estimate</label>\n                  <input type=\"number\" id=\"inspectCostEstimate\" class=\"form-input\" min=\"0\" step=\"1000\" placeholder=\"e.g. 25000\">\n                </div>\n              </div>\n\n              <!-- Protection vs Cost Metric Card -->\n              <div class=\"cost-protection-metric-card\" id=\"costProtectionMetricCard\">\n                <div class=\"metric-row\">\n                  <span class=\"metric-label\">Protection Level:</span>\n                  <span class=\"metric-value font-semibold\" id=\"inspectCostProtectionRating\">High</span>\n                </div>\n                <div class=\"metric-row\">\n                  <span class=\"metric-label\">Cost Efficiency:</span>\n                  <span class=\"metric-value badge\" id=\"inspectCostEfficiencyBadge\">Optimal Protection</span>\n                </div>\n                <div class=\"text-xs text-muted mt-1\" id=\"inspectCostSummaryText\">\n                  Primary defensive barrier mitigating hazard escalation.\n                </div>\n              </div>\n            </div>\n\n            <!-- Detailed Notes -->\n            <div class=\"form-group\">\n              <label for=\"inspectNotes\">Detailed Notes & Operating Procedures</label>\n              <textarea id=\"inspectNotes\" class=\"form-textarea\" rows=\"4\" placeholder=\"Operating context, inspection frequency, maintenance rules...\"></textarea>\n            </div>\n\n            <!-- Element Permissions Overview -->\n            <div class=\"element-permissions-box\">\n              <div class=\"section-badge-header\">\n                <span class=\"sub-label\">Element Specific Permissions</span>\n                <button type=\"button\" id=\"btnManageElemPerm\" class=\"btn btn-xs btn-link\">+ Manage</button>\n              </div>\n              <div id=\"elementPermissionsList\" class=\"permissions-mini-list\">\n                <span class=\"text-xs text-muted\">Inheriting diagram permissions</span>\n              </div>\n            </div>\n\n            <!-- Attached Supporting Documents (PDF, Word, Excel) -->\n            <div class=\"element-attachments-box\" id=\"elementAttachmentsBox\">\n              <div class=\"section-badge-header\">\n                <span class=\"sub-label\">Supporting Documents & Evidence</span>\n                <span id=\"inspectAttachmentCountBadge\" class=\"badge\">0</span>\n              </div>\n              <p class=\"text-xs text-muted mb-2\">Attach verification certs, test results, or operating procedures to this element.</p>\n\n              <!-- Upload Button & File Input -->\n              <div class=\"attachment-upload-zone\" id=\"attachmentDropZone\">\n                <input type=\"file\" id=\"nodeAttachmentInput\" style=\"display: none;\" multiple accept=\".pdf,.doc,.docx,.xls,.xlsx,.png,.jpg,.jpeg,.txt,.csv\">\n                <button type=\"button\" id=\"btnUploadAttachment\" class=\"btn btn-xs btn-outline w-full\" style=\"display: flex; align-items: center; justify-content: center; gap: 6px;\">\n                  <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48\"/></svg>\n                  <span>Attach Document (PDF, Word...)</span>\n                </button>\n              </div>\n\n              <!-- List of attached files -->\n              <div id=\"inspectorAttachmentsList\" class=\"attachments-mini-list\">\n                <span class=\"text-xs text-muted\">No documents attached yet</span>\n              </div>\n            </div>\n\n            <!-- Action buttons -->\n            <div class=\"inspector-actions\">\n              <button type=\"button\" id=\"btnOrderBarriersByTime\" class=\"btn btn-sm btn-outline\" style=\"display: none;\" title=\"Order barriers on this branch by time to barrier (shortest on the left)\">⏱️ Order Barriers by Time</button>\n              <button type=\"button\" id=\"btnSortMitigationsVertically\" class=\"btn btn-sm btn-outline\" style=\"display: none;\" title=\"Align mitigations attached to this escalation factor horizontally\">↔️ Align Mitigations Horizontally</button>\n              <button type=\"button\" id=\"btnCenterOnNode\" class=\"btn btn-sm btn-outline\">Focus on Canvas</button>\n              <button type=\"button\" id=\"btnDeleteActiveNode\" class=\"btn btn-sm btn-danger\">Delete Node</button>\n            </div>\n          </form>\n        </div>\n      </aside>\n    </div>\n\n    <!-- VIEW 3: Enterprise Admin Portal (Tab View) -->\n    <div id=\"adminPortalView\" class=\"admin-portal-view\" style=\"display: none;\">\n      <div class=\"admin-portal-container\">\n        <div class=\"admin-portal-header\">\n          <div class=\"flex items-center gap-2\">\n            <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#7c3aed\" stroke-width=\"2\"><path d=\"M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 14h-2v-2h2zm0-4h-2V7h2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>\n            <h2 style=\"margin: 0; font-size: 1.25rem; font-weight: 700; color: #1e293b;\">Bowtie RISK Admin Portal</h2>\n          </div>\n          <div class=\"flex items-center gap-2\">\n            <button type=\"button\" class=\"btn btn-sm btn-outline\" id=\"btnAdminOpenAuditLog\" title=\"View System Audit Log\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" style=\"margin-right: 4px;\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 14 14\"/></svg>\n              System Audit Log\n            </button>\n            <button type=\"button\" class=\"btn btn-sm btn-outline\" id=\"btnAdminCloseTab\" title=\"Close Admin Tab\">✕ Close</button>\n          </div>\n        </div>\n        <div class=\"admin-portal-body\" style=\"padding-top: 14px;\">\n          <!-- Admin Tabs -->\n          <div class=\"admin-portal-tabs\" id=\"adminPortalTabs\">\n            <button type=\"button\" class=\"admin-tab active\" id=\"tabAdminUsersBtn\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/></svg>\n              User Management\n            </button>\n            <button type=\"button\" class=\"admin-tab\" id=\"tabAdminConfigBtn\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>\n              Instance Configuration\n            </button>\n            <button type=\"button\" class=\"admin-tab\" id=\"tabAdminSuggestionsBtn\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83\"/></svg>\n              Element Suggestions\n            </button>\n            <button type=\"button\" class=\"admin-tab\" id=\"tabAdminLicenseBtn\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\"/></svg>\n              On-Premise License &amp; Updates\n            </button>\n          </div>\n\n          <div id=\"adminPortalAlert\" class=\"auth-wall-error\" style=\"display: none; margin-bottom: 12px;\"></div>\n          <div id=\"adminPortalNotice\" class=\"auth-wall-success\" style=\"display: none; margin-bottom: 12px;\"></div>\n\n          <!-- TAB 1: User Management -->\n          <div class=\"admin-tab-pane active\" id=\"adminUsersPane\">\n            <div class=\"admin-toolbar flex items-center justify-between mb-3\" style=\"gap: 12px; flex-wrap: wrap;\">\n              <div class=\"search-box-wrapper\" style=\"flex: 1; min-width: 200px;\">\n                <input type=\"text\" id=\"adminUserSearchInput\" class=\"form-input text-sm\" placeholder=\"Search users by name, email, or role...\">\n              </div>\n              <div class=\"flex items-center gap-2\">\n                <span class=\"text-xs text-muted\" id=\"adminUserCountBadge\">0 registered users</span>\n                <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnAdminExportUsersCsv\" title=\"Export all users to CSV\">\n                  <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>\n                  Export CSV\n                </button>\n                <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnAdminImportUsersCsv\" title=\"Import users from CSV file\">\n                  <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/></svg>\n                  Import CSV\n                </button>\n                <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnAdminRefreshUsers\" title=\"Refresh user list\">\n                  <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"23 4 23 10 17 10\"/><polyline points=\"1 20 1 14 7 14\"/><path d=\"M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15\"/></svg>\n                  Refresh\n                </button>\n              </div>\n            </div>\n\n            <div class=\"admin-table-container\">\n              <table class=\"admin-table\">\n                <thead>\n                  <tr>\n                    <th>User / Account</th>\n                    <th>Organization & Role</th>\n                    <th>Privilege Level</th>\n                    <th>Account Status</th>\n                    <th>Active Sessions</th>\n                    <th>Actions</th>\n                  </tr>\n                </thead>\n                <tbody id=\"adminUsersTableBody\">\n                  <!-- Populated dynamically via JS -->\n                </tbody>\n              </table>\n            </div>\n          </div>\n\n          <!-- TAB 2: Instance Configuration -->\n          <div class=\"admin-tab-pane\" id=\"adminConfigPane\" style=\"display: none;\">\n            <form id=\"adminConfigForm\" novalidate>\n              <div class=\"admin-config-grid\">\n                <!-- Section A: General Instance Settings -->\n                <div class=\"admin-card\">\n                  <div class=\"admin-card-header\">\n                    <h4>General & Registration Policy</h4>\n                  </div>\n                  <div class=\"admin-card-body\">\n                    <div class=\"form-group\">\n                      <label for=\"cfgInstanceName\">Instance Display Name</label>\n                      <input type=\"text\" id=\"cfgInstanceName\" class=\"form-input\" placeholder=\"e.g. Bowtie RISK Cloud\">\n                    </div>\n                    <div class=\"form-group\">\n                      <label for=\"cfgInstanceUrl\">Instance Public URL</label>\n                      <input type=\"text\" id=\"cfgInstanceUrl\" class=\"form-input\" placeholder=\"https://app.bowtierisk.com or http://localhost:8080\">\n                      <small class=\"text-muted text-xs\">Used for email links and single sign-on redirect resolution.</small>\n                    </div>\n                    <div class=\"form-group\">\n                      <label for=\"cfgRegistrationMode\">User Registration Policy</label>\n                      <select id=\"cfgRegistrationMode\" class=\"form-select\">\n                        <option value=\"open\">Open (Anyone can register an account)</option>\n                        <option value=\"invite_only\">Invite Only (Admins must register accounts)</option>\n                        <option value=\"restricted_domain\">Restricted Domain (@domain.com only)</option>\n                      </select>\n                    </div>\n                    <div class=\"form-group\" id=\"cfgDomainGroup\" style=\"display: none;\">\n                      <label for=\"cfgAllowedDomain\">Restricted Email Domain</label>\n                      <input type=\"text\" id=\"cfgAllowedDomain\" class=\"form-input\" placeholder=\"e.g. company.com or bowtierisk.com\">\n                    </div>\n                    <div class=\"form-group\" style=\"margin-top: 14px; padding-top: 12px; border-top: 1px solid #e2e8f0;\">\n                      <label class=\"toggle-switch-label\" style=\"display: flex; align-items: flex-start; gap: 10px; cursor: pointer;\">\n                        <input type=\"checkbox\" id=\"cfgEnableDirectory\" style=\"margin-top: 3px;\">\n                        <div>\n                          <span style=\"font-weight: 600; font-size: 13px; color: #0f172a; display: block;\">Enable Company Directory & User Discovery</span>\n                          <span class=\"text-muted text-xs\" style=\"display: block; margin-top: 2px;\">\n                            Allows team members to autocomplete colleagues when sharing diagrams or elements. Displays colleague profile names, roles, and avatar images on document access.\n                          </span>\n                        </div>\n                      </label>\n                    </div>\n                  </div>\n                </div>\n\n                <!-- Section B: Outbound SMTP Email Configuration -->\n                <div class=\"admin-card\">\n                  <div class=\"admin-card-header flex items-center justify-between\">\n                    <h4>Email Dispatch (SMTP)</h4>\n                    <label class=\"toggle-switch-label\" style=\"display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 12px; font-weight: 600;\">\n                      <input type=\"checkbox\" id=\"cfgSmtpEnabled\">\n                      <span>Enable SMTP</span>\n                    </label>\n                  </div>\n                  <div class=\"admin-card-body\">\n                    <p class=\"text-xs text-muted mb-3\">\n                      Required to send automated 6-digit password reset codes and notifications to user emails.\n                    </p>\n                    <div class=\"form-group\">\n                      <label for=\"cfgSmtpPreset\">Quick Presets</label>\n                      <select id=\"cfgSmtpPreset\" class=\"form-select\">\n                        <option value=\"custom\">Custom SMTP Server</option>\n                        <option value=\"gmail\">Google Gmail (smtp.gmail.com:587 STARTTLS)</option>\n                        <option value=\"office365\">Microsoft Office 365 (smtp.office365.com:587 STARTTLS)</option>\n                      </select>\n                    </div>\n                    <div class=\"grid-2-col\" style=\"display: grid; grid-template-columns: 2fr 1fr; gap: 8px;\">\n                      <div class=\"form-group\">\n                        <label for=\"cfgSmtpHost\">SMTP Server Host</label>\n                        <input type=\"text\" id=\"cfgSmtpHost\" class=\"form-input\" placeholder=\"smtp.gmail.com\">\n                      </div>\n                      <div class=\"form-group\">\n                        <label for=\"cfgSmtpPort\">Port</label>\n                        <input type=\"number\" id=\"cfgSmtpPort\" class=\"form-input\" placeholder=\"587\">\n                      </div>\n                    </div>\n                    <div class=\"form-group\">\n                      <label for=\"cfgSmtpSecurity\">Encryption / Security</label>\n                      <select id=\"cfgSmtpSecurity\" class=\"form-select\">\n                        <option value=\"starttls\">STARTTLS (Port 587 - Recommended)</option>\n                        <option value=\"ssl_tls\">Direct SSL / TLS (Port 465)</option>\n                        <option value=\"none\">Plaintext (Port 25 - Not recommended)</option>\n                      </select>\n                    </div>\n                    <div class=\"grid-2-col\" style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px;\">\n                      <div class=\"form-group\">\n                        <label for=\"cfgSmtpFromEmail\">Sender From Email</label>\n                        <input type=\"email\" id=\"cfgSmtpFromEmail\" class=\"form-input\" placeholder=\"noreply@bowtierisk.com\">\n                      </div>\n                      <div class=\"form-group\">\n                        <label for=\"cfgSmtpFromName\">Sender Display Name</label>\n                        <input type=\"text\" id=\"cfgSmtpFromName\" class=\"form-input\" placeholder=\"Bowtie RISK Security\">\n                      </div>\n                    </div>\n                    <div class=\"form-group\">\n                      <label for=\"cfgSmtpUsername\">SMTP Auth Username</label>\n                      <input type=\"text\" id=\"cfgSmtpUsername\" class=\"form-input\" placeholder=\"admin@domain.com\">\n                    </div>\n                    <div class=\"form-group\">\n                      <label for=\"cfgSmtpPassword\">SMTP Auth Password / App Password</label>\n                      <input type=\"password\" id=\"cfgSmtpPassword\" class=\"form-input\" placeholder=\"•••••••••••• (Leave blank to keep current)\">\n                      <small class=\"text-muted text-xs\">For Gmail, generate and paste a 16-character Google App Password.</small>\n                    </div>\n\n                    <!-- SMTP Test Email Box -->\n                    <div class=\"smtp-test-box mt-3 p-3\" style=\"background: #f8fafc; border: 1px dashed #cbd5e1; border-radius: 6px;\">\n                      <label style=\"font-size: 11px; font-weight: 600; text-transform: uppercase; color: #475569; display: block; margin-bottom: 4px;\">\n                        Verify SMTP Delivery\n                      </label>\n                      <div class=\"flex items-center gap-2\">\n                        <input type=\"email\" id=\"cfgTestEmailTarget\" class=\"form-input text-xs\" placeholder=\"recipient@example.com\" style=\"flex: 1;\">\n                        <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnAdminTestEmail\" style=\"white-space: nowrap;\">\n                          Send Test Email\n                        </button>\n                      </div>\n                      <div id=\"smtpTestStatus\" class=\"mt-2 text-xs\" style=\"display: none;\"></div>\n                    </div>\n                  </div>\n                </div>\n\n                <!-- Section C: Single Sign-On (SSO) Configuration -->\n                <div class=\"admin-card\" style=\"grid-column: 1 / -1;\">\n                  <div class=\"admin-card-header flex items-center justify-between\">\n                    <h4>Single Sign-On (SSO / OAuth2)</h4>\n                    <span class=\"badge\" style=\"background: #e0e7ff; color: #4338ca;\">Enterprise Ready</span>\n                  </div>\n                  <div class=\"admin-card-body\">\n                    <p class=\"text-xs text-muted mb-3\">\n                      Configure corporate Single Sign-On providers so team members can authenticate securely using existing corporate accounts.\n                    </p>\n                    <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 16px;\">\n                      <!-- Google SSO -->\n                      <div class=\"p-3\" style=\"border: 1px solid #e2e8f0; border-radius: 6px; background: #fafafa;\">\n                        <div class=\"flex items-center justify-between mb-2\">\n                          <strong class=\"text-sm\">Google Workspace</strong>\n                          <label style=\"display: flex; align-items: center; gap: 4px; font-size: 11px; cursor: pointer;\">\n                            <input type=\"checkbox\" id=\"cfgGoogleSsoEnabled\">\n                            <span>Enable</span>\n                          </label>\n                        </div>\n                        <div class=\"form-group mb-2\">\n                          <label class=\"text-xs\" for=\"cfgGoogleClientId\">Client ID</label>\n                          <input type=\"text\" id=\"cfgGoogleClientId\" class=\"form-input text-xs\" placeholder=\"xxxx.apps.googleusercontent.com\">\n                        </div>\n                        <div class=\"form-group mb-2\">\n                          <label class=\"text-xs\" for=\"cfgGoogleClientSecret\">Client Secret</label>\n                          <input type=\"password\" id=\"cfgGoogleClientSecret\" class=\"form-input text-xs\" placeholder=\"•••••••••••• (Leave blank to keep current)\">\n                        </div>\n                        <div class=\"form-group mb-0\">\n                          <label class=\"text-xs\" for=\"cfgGoogleRedirectUri\">Redirect URI</label>\n                          <input type=\"text\" id=\"cfgGoogleRedirectUri\" class=\"form-input text-xs\" placeholder=\"https://app.bowtierisk.com/api/auth/oauth/google/callback\">\n                        </div>\n                      </div>\n\n                      <!-- Microsoft SSO -->\n                      <div class=\"p-3\" style=\"border: 1px solid #e2e8f0; border-radius: 6px; background: #fafafa;\">\n                        <div class=\"flex items-center justify-between mb-2\">\n                          <strong class=\"text-sm\">Microsoft Entra ID (Azure AD)</strong>\n                          <label style=\"display: flex; align-items: center; gap: 4px; font-size: 11px; cursor: pointer;\">\n                            <input type=\"checkbox\" id=\"cfgMicrosoftSsoEnabled\">\n                            <span>Enable</span>\n                          </label>\n                        </div>\n                        <div class=\"form-group mb-2\">\n                          <label class=\"text-xs\" for=\"cfgMicrosoftClientId\">Client ID (Application ID)</label>\n                          <input type=\"text\" id=\"cfgMicrosoftClientId\" class=\"form-input text-xs\" placeholder=\"00000000-0000-0000-0000-000000000000\">\n                        </div>\n                        <div class=\"form-group mb-2\">\n                          <label class=\"text-xs\" for=\"cfgMicrosoftClientSecret\">Client Secret</label>\n                          <input type=\"password\" id=\"cfgMicrosoftClientSecret\" class=\"form-input text-xs\" placeholder=\"•••••••••••• (Leave blank to keep current)\">\n                        </div>\n                        <div class=\"form-group mb-0\">\n                          <label class=\"text-xs\" for=\"cfgMicrosoftTenantId\">Tenant ID</label>\n                          <input type=\"text\" id=\"cfgMicrosoftTenantId\" class=\"form-input text-xs\" placeholder=\"common or organizations\">\n                        </div>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n\n              <div class=\"admin-config-actions mt-4 flex items-center justify-between\" style=\"border-top: 1px solid #e2e8f0; padding-top: 14px;\">\n                <button type=\"button\" class=\"btn btn-outline\" id=\"btnAdminReloadConfig\">Discard Unsaved Changes</button>\n                <button type=\"submit\" class=\"btn btn-primary\" id=\"btnAdminSaveConfig\">Save Instance Configuration</button>\n              </div>\n            </form>\n          </div>\n\n          <!-- TAB 3: Element Suggestions Pool Manager -->\n          <div class=\"admin-tab-pane\" id=\"adminSuggestionsPane\" style=\"display: none;\">\n            <div class=\"admin-card mb-3\" style=\"background: #f8fafc; border: 1px solid #e2e8f0;\">\n              <div class=\"admin-card-body\" style=\"padding: 16px;\">\n                <div class=\"flex items-center justify-between\" style=\"flex-wrap: wrap; gap: 12px;\">\n                  <div>\n                    <h4 style=\"margin: 0; font-size: 15px; color: #0f172a; display: flex; align-items: center; gap: 8px;\">\n                      <span>💡</span> Diagram Element Suggestions Pool\n                    </h4>\n                    <p class=\"text-xs text-muted\" style=\"margin: 4px 0 0 0; max-width: 650px;\">\n                      Configure realistic engineering example text displayed as grey placeholders inside new bowtie elements. When an element is added to the diagram, an example is randomly selected from its type pool to guide risk assessors.\n                    </p>\n                  </div>\n                  <div class=\"flex items-center gap-2\">\n                    <button type=\"button\" class=\"btn btn-sm btn-outline\" id=\"btnAdminResetSuggestions\">\n                      Reset to Defaults\n                    </button>\n                    <button type=\"button\" class=\"btn btn-sm btn-primary\" id=\"btnAdminSaveSuggestions\">\n                      Save Suggestions Pool\n                    </button>\n                  </div>\n                </div>\n              </div>\n            </div>\n\n            <!-- Suggestions Cards Grid -->\n            <div class=\"admin-suggestions-grid\" id=\"adminSuggestionsGrid\">\n              <!-- Dynamically populated by app.js renderAdminSuggestions() -->\n            </div>\n          </div>\n\n          <!-- TAB 4: On-Premise License & System Updates -->\n          <div class=\"admin-tab-pane\" id=\"adminLicensePane\" style=\"display: none;\">\n            <div class=\"admin-config-grid\">\n              <!-- Card 1: Instance License Key / File (.btlic) -->\n              <div class=\"admin-card\">\n                <div class=\"admin-card-header flex items-center justify-between\">\n                  <div class=\"flex items-center gap-2\">\n                    <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2563eb\" stroke-width=\"2\"><path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\"/></svg>\n                    <h4 style=\"margin: 0;\">Instance License Key / File (<code>.btlic</code>)</h4>\n                  </div>\n                  <span id=\"adminLicenseStatusBadge\" class=\"badge\" style=\"background: #dcfce7; color: #166534;\">Checking...</span>\n                </div>\n                <div class=\"admin-card-body\">\n                  <p class=\"text-xs text-muted mb-3\">\n                    Self-hosted / on-premise deployments require a cryptographically signed license key or <code>.btlic</code> file to enable creating and editing Bowtie diagrams. Without an active license, the instance operates in read-only mode.\n                  </p>\n\n                  <!-- Active License Metadata Box -->\n                  <div id=\"adminLicenseMetadataBox\" style=\"background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; margin-bottom: 14px; font-size: 12px;\">\n                    <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px;\">\n                      <div>\n                        <span class=\"text-muted\" style=\"display: block; font-size: 11px;\">Deployment Mode</span>\n                        <strong id=\"adminLicenseModeLabel\" style=\"color: #0f172a;\">Cloud Hosted</strong>\n                      </div>\n                      <div>\n                        <span class=\"text-muted\" style=\"display: block; font-size: 11px;\">Editing Status</span>\n                        <strong id=\"adminLicenseEditLabel\" style=\"color: #16a34a;\">Unlocked (Full Write)</strong>\n                      </div>\n                      <div>\n                        <span class=\"text-muted\" style=\"display: block; font-size: 11px;\">Licensed Organization</span>\n                        <strong id=\"adminLicenseOrgLabel\" style=\"color: #0f172a;\">—</strong>\n                      </div>\n                      <div>\n                        <span class=\"text-muted\" style=\"display: block; font-size: 11px;\">License ID / Tier</span>\n                        <strong id=\"adminLicenseIdLabel\" style=\"color: #0f172a; font-family: monospace;\">—</strong>\n                      </div>\n                      <div>\n                        <span class=\"text-muted\" style=\"display: block; font-size: 11px;\">Valid Until</span>\n                        <strong id=\"adminLicenseExpiresLabel\" style=\"color: #0f172a;\">—</strong>\n                      </div>\n                      <div>\n                        <span class=\"text-muted\" style=\"display: block; font-size: 11px;\">Seat Limit</span>\n                        <strong id=\"adminLicenseSeatsLabel\" style=\"color: #0f172a;\">Unlimited</strong>\n                      </div>\n                    </div>\n                    <div id=\"adminLicenseStatusMsg\" class=\"text-xs mt-2\" style=\"color: #475569; padding-top: 8px; border-top: 1px solid #e2e8f0;\"></div>\n                  </div>\n\n                  <div class=\"form-group\">\n                    <label class=\"text-xs font-semibold\" for=\"adminLicenseFileInput\">Option A: Upload <code>.btlic</code> License File</label>\n                    <input type=\"file\" id=\"adminLicenseFileInput\" accept=\".btlic,.json,.txt\" class=\"form-input text-xs\">\n                  </div>\n\n                  <div class=\"form-group\">\n                    <label class=\"text-xs font-semibold\" for=\"adminLicenseKeyInput\">Option B: Or Paste License Key / <code>.btlic</code> JSON</label>\n                    <textarea id=\"adminLicenseKeyInput\" class=\"form-textarea text-xs\" rows=\"4\" placeholder=\"Paste BTRISK-v1.... key or .btlic JSON contents here\" style=\"font-family: monospace;\"></textarea>\n                  </div>\n\n                  <div class=\"flex items-center justify-between gap-2 mt-3\" style=\"flex-wrap: wrap;\">\n                    <div class=\"flex items-center gap-2\">\n                      <button type=\"button\" class=\"btn btn-primary btn-sm\" id=\"btnAdminActivateLicense\">\n                        Verify &amp; Activate License\n                      </button>\n                      <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnAdminDeactivateLicense\" style=\"color: #dc2626; border-color: #fecaca;\">\n                        Remove License\n                      </button>\n                    </div>\n                    <a href=\"https://bowtierisk.com/on-premise\" target=\"_blank\" rel=\"noopener\" class=\"text-xs\" style=\"color: #2563eb; font-weight: 600; text-decoration: underline;\">\n                      Get Free License Key at Portal ↗\n                    </a>\n                  </div>\n                </div>\n              </div>\n\n              <!-- Card 2: System Updates (Automatic Online & Offline .btupdate Import) -->\n              <div class=\"admin-card\">\n                <div class=\"admin-card-header flex items-center justify-between\">\n                  <div class=\"flex items-center gap-2\">\n                    <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#059669\" stroke-width=\"2\"><polyline points=\"23 4 23 10 17 10\"/><polyline points=\"1 20 1 14 7 14\"/><path d=\"M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15\"/></svg>\n                    <h4 style=\"margin: 0;\">System Updates (Auto &amp; Offline <code>.btupdate</code>)</h4>\n                  </div>\n                  <span id=\"adminUpdateVersionBadge\" class=\"badge\" style=\"background: #e0f2fe; color: #0369a1; font-family: monospace;\">v1.1.1111</span>\n                </div>\n                <div class=\"admin-card-body\">\n                  <p class=\"text-xs text-muted mb-3\">\n                    Manage how your self-hosted instance receives application updates. Online instances can enable automatic updates, while air-gapped / offline networks can import signed <code>.btupdate</code> packages.\n                  </p>\n\n                  <!-- Automatic Online Updates Toggle -->\n                  <div style=\"background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; margin-bottom: 14px;\">\n                    <label class=\"toggle-switch-label\" style=\"display: flex; align-items: flex-start; gap: 10px; cursor: pointer;\">\n                      <input type=\"checkbox\" id=\"adminAutoUpdateToggle\" style=\"margin-top: 3px; width: 16px; height: 16px; accent-color: #2563eb;\">\n                      <div style=\"flex: 1;\">\n                        <span style=\"font-weight: 600; font-size: 13px; color: #0f172a; display: block;\">Enable Automatic Online Updates</span>\n                        <span class=\"text-muted text-xs\" style=\"display: block; margin-top: 2px;\">\n                          Automatically checks the upstream release feed and installs cryptographically verified updates with pre-update backup snapshots.\n                        </span>\n                      </div>\n                    </label>\n                    <div class=\"form-group mt-2 mb-2\">\n                      <label class=\"text-xs\" for=\"adminUpdateServerUrlInput\">Update Server URL</label>\n                      <div class=\"flex items-center gap-2\">\n                        <input type=\"text\" id=\"adminUpdateServerUrlInput\" class=\"form-input text-xs\" value=\"https://bowtierisk.com\" placeholder=\"https://bowtierisk.com\" style=\"flex: 1; font-family: monospace;\">\n                        <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnAdminSaveUpdateSettings\" style=\"white-space: nowrap;\">\n                          Save Policy\n                        </button>\n                      </div>\n                    </div>\n                    <div class=\"flex items-center justify-between mt-2 pt-2\" style=\"border-top: 1px solid #e2e8f0; flex-wrap: wrap; gap: 8px;\">\n                      <span class=\"text-xs text-muted\" id=\"adminUpdateLastCheckLabel\">Installed overlay: Built-in image</span>\n                      <button type=\"button\" class=\"btn btn-primary btn-sm\" id=\"btnAdminApplyOnlineUpdate\" style=\"font-size: 11px;\">\n                        Check &amp; Apply Online Update Now\n                      </button>\n                    </div>\n                  </div>\n\n                  <!-- Offline / Air-Gapped Update Bundle Import (.btupdate) -->\n                  <div style=\"background: #fffbeb; border: 1px solid #fde68a; border-radius: 8px; padding: 12px;\">\n                    <div class=\"text-xs font-semibold mb-1\" style=\"color: #92400e; display: flex; align-items: center; gap: 6px;\">\n                      <span>📦</span> Air-Gapped / Offline Update Import (<code>.btupdate</code>)\n                    </div>\n                    <p class=\"text-xs text-muted mb-2\" style=\"color: #78350f;\">\n                      Running on an isolated network without internet access? Download the latest signed <code>.btupdate</code> bundle from <code>bowtierisk.com/on-premise</code> and import it below.\n                    </p>\n                    <div class=\"form-group mb-2\">\n                      <input type=\"file\" id=\"adminOfflineUpdateFileInput\" accept=\".btupdate,.json\" class=\"form-input text-xs\" style=\"background: #ffffff;\">\n                    </div>\n                    <div class=\"flex items-center justify-between gap-2\" style=\"flex-wrap: wrap;\">\n                      <button type=\"button\" class=\"btn btn-primary btn-sm\" id=\"btnAdminImportOfflineUpdate\" style=\"background: #d97706; border-color: #d97706; font-size: 11px;\">\n                        Verify &amp; Apply Offline Update File\n                      </button>\n                      <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnAdminRollbackUpdate\" style=\"font-size: 11px; color: #475569;\">\n                        Rollback to Built-In Version\n                      </button>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div class=\"admin-portal-footer\">\n          <span class=\"text-xs text-muted\">Bowtie RISK Instance Administration & Security</span>\n          <div class=\"flex-spacer\"></div>\n          <button type=\"button\" class=\"btn btn-secondary\" id=\"btnAdminCloseTabFooter\">Close</button>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 1: User Settings (Custom Names & Colors) -->\n    <div class=\"modal-overlay\" id=\"modalSettings\">\n      <div class=\"modal-box modal-lg\">\n        <div class=\"modal-header\">\n          <h3>Customize Item Names & Colors</h3>\n          <button class=\"modal-close\" data-close=\"modalSettings\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <p class=\"text-muted text-sm mb-4\">\n            Customize the displayed nomenclature and visual color palette across your Bowtie Risk diagrams. Changes apply in real time and are saved to your account.\n          </p>\n          <div class=\"customization-table-wrap\">\n            <table class=\"customization-table\">\n              <thead>\n                <tr>\n                  <th>System Category</th>\n                  <th>Your Custom Name</th>\n                  <th>Theme Color</th>\n                  <th>Preview</th>\n                </tr>\n              </thead>\n              <tbody id=\"settingsTableBody\">\n                <!-- Dynamically generated for each NodeType -->\n              </tbody>\n            </table>\n          </div>\n        </div>\n        <div class=\"modal-footer\">\n          <button class=\"btn btn-outline\" id=\"btnResetSettingsDefault\">Reset Defaults</button>\n          <div class=\"flex-spacer\"></div>\n          <button class=\"btn btn-secondary\" data-close=\"modalSettings\">Cancel</button>\n          <button class=\"btn btn-primary\" id=\"btnSaveSettings\">Save Customizations</button>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 2: Version History & Audit Log (Google Sheets Style) -->\n    <div class=\"modal-overlay\" id=\"modalHistory\">\n      <div class=\"modal-box modal-lg\">\n        <div class=\"modal-header\">\n          <h3>Version & Change History</h3>\n          <button class=\"modal-close\" data-close=\"modalHistory\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <p class=\"text-muted text-sm mb-3\">\n            Real-time audit log of all changes made across diagrams, elements, and access permissions.\n          </p>\n          <div class=\"audit-log-toolbar\">\n            <input type=\"text\" id=\"auditSearchInput\" placeholder=\"Filter audit trail by user, element, or action...\">\n            <button id=\"btnRefreshAudit\" class=\"btn btn-sm btn-outline\">Refresh</button>\n          </div>\n          <div class=\"audit-log-container\">\n            <table class=\"audit-table\">\n              <thead>\n                <tr>\n                  <th>Timestamp</th>\n                  <th>User</th>\n                  <th>Action</th>\n                  <th>Target</th>\n                  <th>Change Summary</th>\n                  <th style=\"width: 100px; text-align: center;\">Revert</th>\n                </tr>\n              </thead>\n              <tbody id=\"auditTableBody\">\n                <!-- Dynamically populated audit entries -->\n              </tbody>\n            </table>\n          </div>\n        </div>\n        <div class=\"modal-footer\">\n          <button class=\"btn btn-primary\" data-close=\"modalHistory\">Close</button>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 3: Share & Permissions (Google Drive Sharing Layout) -->\n    <div class=\"modal-overlay\" id=\"modalShare\">\n      <div class=\"modal-box gdrive-share-modal\">\n        <!-- Google Drive Header -->\n        <div class=\"gdrive-share-header\">\n          <div class=\"gdrive-share-title-wrap\">\n            <h3 id=\"shareModalTitle\" class=\"gdrive-share-title\">Share 'Untitled document'</h3>\n          </div>\n          <div class=\"gdrive-share-header-actions\">\n            <button type=\"button\" class=\"gdrive-icon-btn\" id=\"btnShareHelp\" title=\"Help on sharing\">\n              <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/></svg>\n            </button>\n            <button type=\"button\" class=\"gdrive-icon-btn\" id=\"btnShareSettings\" title=\"Share settings\">\n              <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>\n            </button>\n            <button type=\"button\" class=\"modal-close gdrive-icon-btn\" data-close=\"modalShare\" title=\"Close\">✕</button>\n          </div>\n        </div>\n\n        <div class=\"gdrive-share-body\">\n          <!-- Outlined Add People Box with Autocomplete -->\n          <div class=\"gdrive-search-box-wrap\" id=\"shareAddPeopleBox\">\n            <div class=\"gdrive-search-box\" id=\"shareInputOutline\">\n              <input type=\"text\" id=\"permUserEmail\" class=\"gdrive-search-input\" placeholder=\"Add people, groups, and email addresses\" autocomplete=\"off\">\n            </div>\n            <div id=\"permUserAutocompleteList\" class=\"user-autocomplete-dropdown gdrive-autocomplete\" style=\"display: none;\"></div>\n          </div>\n\n          <!-- Active Inviting Row / Expandable Panel (when recipient is picked or typed) -->\n          <form id=\"addPermissionForm\" class=\"gdrive-invite-panel\" style=\"display: none;\" novalidate>\n            <div class=\"gdrive-invite-main-row\">\n              <div class=\"gdrive-recipient-chip\" id=\"shareRecipientChip\">\n                <span id=\"shareRecipientChipText\"></span>\n                <button type=\"button\" id=\"btnClearRecipient\" class=\"btn-chip-remove\" title=\"Remove recipient\">✕</button>\n              </div>\n              <div class=\"gdrive-invite-selectors\">\n                <!-- Scope Selector -->\n                <select id=\"permTargetTypeSelect\" class=\"gdrive-role-dropdown\" title=\"Scope of permission\">\n                  <option value=\"diagram\">Active Diagram</option>\n                  <option value=\"folder\">Entire Folder</option>\n                  <option value=\"element\">Specific Element</option>\n                </select>\n                <!-- Access Level Role Selector -->\n                <select id=\"permAccessLevel\" class=\"gdrive-role-dropdown\" title=\"Access role\">\n                  <option value=\"read\">Viewer</option>\n                  <option value=\"write\">Editor</option>\n                  <option value=\"admin\">Admin</option>\n                </select>\n              </div>\n            </div>\n\n            <!-- Granular sub-selectors for Folder or Element -->\n            <div id=\"permFolderSelectGroup\" class=\"gdrive-sub-selector\" style=\"display: none;\">\n              <label for=\"permTargetFolder\" class=\"gdrive-sub-label\">Folder:</label>\n              <select id=\"permTargetFolder\" class=\"form-select\"></select>\n            </div>\n            <div id=\"permElementSelectGroup\" class=\"gdrive-sub-selector\" style=\"display: none;\">\n              <label for=\"permTargetElement\" class=\"gdrive-sub-label\">Element:</label>\n              <select id=\"permTargetElement\" class=\"form-select\"></select>\n            </div>\n\n            <!-- Optional notify message -->\n            <div class=\"gdrive-message-wrap\">\n              <input type=\"text\" id=\"shareInviteNote\" class=\"gdrive-message-input\" placeholder=\"Notify with optional note...\">\n            </div>\n\n            <div class=\"gdrive-invite-actions\">\n              <button type=\"button\" id=\"btnCancelInvite\" class=\"btn-gdrive-cancel\">Cancel</button>\n              <button type=\"submit\" id=\"btnSendInvite\" class=\"btn-gdrive-send\">Share</button>\n            </div>\n          </form>\n\n          <!-- Hidden radio group for full backwards-compatibility with any radio selectors -->\n          <div style=\"display: none;\">\n            <input type=\"radio\" name=\"permTargetType\" value=\"diagram\" id=\"permTargetTypeDiagram\" checked>\n            <input type=\"radio\" name=\"permTargetType\" value=\"folder\" id=\"permTargetTypeFolder\">\n            <input type=\"radio\" name=\"permTargetType\" value=\"element\" id=\"permTargetTypeElement\">\n            <tbody id=\"permissionsTableBody\"></tbody>\n          </div>\n\n          <!-- People with access Section -->\n          <div class=\"gdrive-section\">\n            <h4 class=\"gdrive-section-title\">People with access</h4>\n            <div class=\"gdrive-user-list\" id=\"sharePeopleWithAccessList\">\n              <!-- Dynamically populated: Owner at top, followed by collaborators -->\n            </div>\n          </div>\n\n          <!-- General access Section -->\n          <div class=\"gdrive-section gdrive-general-access-section\">\n            <h4 class=\"gdrive-section-title\">General access</h4>\n            <div class=\"gdrive-general-access-row\">\n              <div class=\"gdrive-general-access-icon\">\n                <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/></svg>\n              </div>\n              <div class=\"gdrive-general-access-info\">\n                <div class=\"gdrive-general-access-header\">\n                  <select id=\"shareGeneralAccessSelect\" class=\"gdrive-access-selector\" title=\"General access level\">\n                    <option value=\"restricted\">Restricted</option>\n                    <option value=\"organization\">Anyone in company</option>\n                  </select>\n                </div>\n                <div class=\"gdrive-general-access-desc\" id=\"shareGeneralAccessDesc\">\n                  Only people with access can open with the link\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <!-- Google Drive Footer / Action Bar -->\n        <div class=\"gdrive-share-footer\">\n          <button type=\"button\" id=\"btnShareCopyLink\" class=\"btn-gdrive-copy-link\" title=\"Copy shareable link\">\n            <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg>\n            <span id=\"shareCopyLinkText\">Copy link</span>\n          </button>\n          <button type=\"button\" id=\"btnShareDone\" class=\"btn-gdrive-done\" data-close=\"modalShare\">\n            Done\n          </button>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 4: User Authentication (Sign In & Sign Up) -->\n    <div class=\"modal-overlay\" id=\"modalAuth\">\n      <div class=\"modal-box modal-sm\">\n        <div class=\"modal-header\">\n          <h3>Account Access</h3>\n          <button class=\"modal-close\" data-close=\"modalAuth\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <div class=\"auth-tabs\" id=\"modalAuthTabs\">\n            <button type=\"button\" class=\"auth-tab active\" id=\"tabAuthLoginBtn\">Sign In</button>\n            <button type=\"button\" class=\"auth-tab\" id=\"tabAuthSignupBtn\">Create Account</button>\n          </div>\n          <div id=\"modalAuthError\" class=\"auth-wall-error\" style=\"display: none; margin-bottom: 12px;\"></div>\n          <div id=\"modalAuthSuccess\" class=\"auth-wall-success\" style=\"display: none; margin-bottom: 12px;\"></div>\n\n          <!-- Sign In Form -->\n          <form id=\"authLoginForm\" class=\"auth-form-pane active\" novalidate>\n            <p class=\"text-muted text-xs mb-3\">Sign in with your email and password to access your cloud risk assessments.</p>\n            <div class=\"form-group\">\n              <label for=\"authLoginEmail\">Email Address</label>\n              <input type=\"email\" id=\"authLoginEmail\" class=\"form-input\" placeholder=\"name@company.com\" autofocus>\n            </div>\n            <div class=\"form-group\">\n              <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;\">\n                <label for=\"authLoginPassword\" style=\"margin-bottom: 0;\">Password</label>\n                <a href=\"#\" id=\"linkModalForgotPassword\" style=\"font-size: 11px; color: #2563eb; text-decoration: underline;\">Forgot password?</a>\n              </div>\n              <input type=\"password\" id=\"authLoginPassword\" class=\"form-input\" placeholder=\"Enter your password\">\n            </div>\n            <button type=\"submit\" class=\"btn btn-primary w-full\" id=\"btnSubmitLogin\">Sign In</button>\n            <div class=\"text-center mt-3 text-xs text-muted\">\n              Don't have an account? <a href=\"#\" id=\"linkSwitchToSignup\" style=\"color: #2563eb; text-decoration: underline;\">Create an account</a>\n            </div>\n          </form>\n\n          <!-- Create Account Form -->\n          <form id=\"authSignupForm\" class=\"auth-form-pane\" style=\"display: none;\" novalidate>\n            <p class=\"text-muted text-xs mb-3\">Join Bowtie RISK to create assessments, customize barriers, and collaborate with your engineering team.</p>\n            <div class=\"form-group\">\n              <label for=\"authSignupName\">Full Name</label>\n              <input type=\"text\" id=\"authSignupName\" class=\"form-input\" placeholder=\"e.g. David J. Bowtie\">\n            </div>\n            <div class=\"form-group\">\n              <label for=\"authSignupEmail\">Work Email</label>\n              <input type=\"email\" id=\"authSignupEmail\" class=\"form-input\" placeholder=\"name@company.com\">\n            </div>\n            <div class=\"form-group\">\n              <label for=\"authSignupPassword\">Password</label>\n              <input type=\"password\" id=\"authSignupPassword\" class=\"form-input\" placeholder=\"Choose a password (min 4 chars)\">\n            </div>\n            <div class=\"form-group\">\n              <label for=\"authSignupOrg\">Organization / Company</label>\n              <input type=\"text\" id=\"authSignupOrg\" class=\"form-input\" placeholder=\"e.g. Offshore Facilities Ltd\">\n            </div>\n            <div class=\"form-group\">\n              <label for=\"authSignupRole\">Role / Title</label>\n              <input type=\"text\" id=\"authSignupRole\" class=\"form-input\" placeholder=\"e.g. Risk Assessor\">\n            </div>\n            <button type=\"submit\" class=\"btn btn-primary w-full\" id=\"btnSubmitSignup\">Create Account</button>\n            <div class=\"text-center mt-3 text-xs text-muted\">\n              Already have an account? <a href=\"#\" id=\"linkSwitchToLogin\" style=\"color: #2563eb; text-decoration: underline;\">Sign In</a>\n            </div>\n          </form>\n\n          <!-- Modal Forgot Password Form (Step 1) -->\n          <form id=\"modalForgotForm\" class=\"auth-form-pane\" style=\"display: none;\" novalidate>\n            <p class=\"text-muted text-xs mb-3\">Enter your registered email to receive a 6-digit verification code to reset your password.</p>\n            <div class=\"form-group\">\n              <label for=\"modalForgotEmail\">Registered Email</label>\n              <input type=\"email\" id=\"modalForgotEmail\" class=\"form-input\" placeholder=\"name@company.com\">\n            </div>\n            <button type=\"submit\" id=\"btnModalSubmitForgot\" class=\"btn btn-primary w-full\">Send Verification Code</button>\n            <div class=\"text-center mt-3 text-xs text-muted\">\n              <a href=\"#\" id=\"linkModalBackToLoginFromForgot\" style=\"color: #2563eb; text-decoration: underline;\">← Back to Sign In</a>\n            </div>\n          </form>\n\n          <!-- Modal Reset Password Form (Step 2) -->\n          <form id=\"modalResetForm\" class=\"auth-form-pane\" style=\"display: none;\" novalidate>\n            <div class=\"form-group\">\n              <label for=\"modalResetEmail\">Account Email</label>\n              <input type=\"email\" id=\"modalResetEmail\" class=\"form-input\" readonly style=\"background: #f1f5f9;\">\n            </div>\n            <div class=\"form-group\">\n              <label for=\"modalResetCode\">6-Digit Verification Code</label>\n              <input type=\"text\" id=\"modalResetCode\" class=\"form-input\" placeholder=\"123456\" maxlength=\"6\" style=\"letter-spacing: 4px; font-weight: bold; font-family: monospace; font-size: 15px; text-align: center;\">\n            </div>\n            <div class=\"form-group\">\n              <label for=\"modalResetNewPassword\">New Password</label>\n              <input type=\"password\" id=\"modalResetNewPassword\" class=\"form-input\" placeholder=\"Min. 4 characters\">\n            </div>\n            <div class=\"form-group\">\n              <label for=\"modalResetConfirmPassword\">Confirm New Password</label>\n              <input type=\"password\" id=\"modalResetConfirmPassword\" class=\"form-input\" placeholder=\"Re-enter new password\">\n            </div>\n            <button type=\"submit\" id=\"btnModalSubmitReset\" class=\"btn btn-primary w-full\">Set New Password & Sign In</button>\n            <div class=\"text-center mt-3 text-xs text-muted\" style=\"display: flex; justify-content: space-between;\">\n              <a href=\"#\" id=\"linkModalResendCode\" style=\"color: #2563eb; text-decoration: underline;\">Request new code</a>\n              <a href=\"#\" id=\"linkModalBackToLoginFromReset\" style=\"color: #2563eb; text-decoration: underline;\">← Cancel</a>\n            </div>\n          </form>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 5: Global Element Sync Prompt -->\n    <div class=\"modal-overlay\" id=\"modalGlobalSyncPrompt\">\n      <div class=\"modal-box\">\n        <div class=\"modal-header\">\n          <h3>Update Global Element Instances</h3>\n          <button class=\"modal-close\" data-close=\"modalGlobalSyncPrompt\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <div class=\"alert-info-box\">\n            <strong id=\"syncElementName\">Deluge & Fire Water Monitor System</strong>\n            <p id=\"syncInstancesCountText\">This element appears in 2 locations across your diagrams.</p>\n          </div>\n          <p class=\"text-sm text-muted mt-3\">\n            Do you want this change applied to <strong>all instances</strong> of this element, or do you want to keep this change specific to <strong>only this instance</strong>?\n          </p>\n        </div>\n        <div class=\"modal-footer\">\n          <button class=\"btn btn-outline\" id=\"btnSyncOnlyThisInstance\">Update Only This Instance</button>\n          <button class=\"btn btn-primary\" id=\"btnSyncAllInstances\">Update All Instances</button>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 6: Create New Diagram -->\n    <div class=\"modal-overlay\" id=\"modalNewDiagram\">\n      <div class=\"modal-box modal-sm\">\n        <div class=\"modal-header\">\n          <h3 id=\"modalNewDiagramHeading\">Create New Bowtie Diagram</h3>\n          <button class=\"modal-close\" data-close=\"modalNewDiagram\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <form id=\"newDiagramForm\" novalidate>\n            <div class=\"form-group mb-3\" id=\"groupAddToCurrentDiagram\" style=\"background: #f8fafc; padding: 10px 12px; border-radius: 6px; border: 1px solid #e2e8f0;\">\n              <label class=\"flex items-center gap-2 cursor-pointer\" style=\"font-weight: 600; color: #1e293b; margin: 0; user-select: none;\">\n                <input type=\"checkbox\" id=\"chkAddToCurrentDiagram\" style=\"width: 16px; height: 16px; accent-color: #2563eb; cursor: pointer;\">\n                <span>Add Hazard & Top Event to current diagram</span>\n              </label>\n              <small class=\"text-xs text-muted\" style=\"display: block; margin-top: 4px; margin-left: 24px;\">\n                Adds a new Hazard & Top Event cluster to the active assessment tab instead of creating a new assessment.\n              </small>\n            </div>\n            <div class=\"form-group\" id=\"groupNewDiagTitle\">\n              <label for=\"newDiagTitle\">Diagram Title</label>\n              <input type=\"text\" id=\"newDiagTitle\" class=\"form-input\" placeholder=\"e.g. Unit 200 Distillation Column\">\n            </div>\n            <div class=\"form-group\" id=\"groupNewDiagDesc\">\n              <label for=\"newDiagDesc\">Scope & Description</label>\n              <textarea id=\"newDiagDesc\" class=\"form-textarea\" rows=\"2\" placeholder=\"Assessment scope...\"></textarea>\n            </div>\n            <div class=\"form-group\">\n              <label for=\"newDiagHazard\">Hazard Name</label>\n              <input type=\"text\" id=\"newDiagHazard\" class=\"form-input\" placeholder=\"e.g. Flammable Liquid / Gas\">\n            </div>\n            <div class=\"form-group mb-3\">\n              <label for=\"newDiagTopEvent\">Top Event Name</label>\n              <input type=\"text\" id=\"newDiagTopEvent\" class=\"form-input\" placeholder=\"e.g. Loss of Primary Containment\">\n            </div>\n            <button type=\"submit\" class=\"btn btn-primary w-full mt-3\" id=\"btnSubmitNewDiagram\">Create Diagram on Canvas</button>\n          </form>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 7: Create Master Global Element -->\n    <div class=\"modal-overlay\" id=\"modalNewGlobalElem\">\n      <div class=\"modal-box modal-sm\">\n        <div class=\"modal-header\">\n          <h3>Add Reusable Master Element</h3>\n          <button class=\"modal-close\" data-close=\"modalNewGlobalElem\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <form id=\"newGlobalElemForm\" novalidate>\n            <div class=\"form-group\">\n              <label for=\"geName\">Element Name</label>\n              <input type=\"text\" id=\"geName\" class=\"form-input\" placeholder=\"e.g. Emergency Shutdown Valve (ESDV)\">\n            </div>\n            <div class=\"form-group\">\n              <label for=\"geType\">Category</label>\n              <select id=\"geType\" class=\"form-select\">\n                <option value=\"threat_barrier\">Threat Barrier (Prevention)</option>\n                <option value=\"outcome_barrier\">Outcome Barrier (Mitigation)</option>\n                <option value=\"mitigation\">Mitigation Control</option>\n                <option value=\"threat\">Reusable Threat</option>\n              </select>\n            </div>\n            <div class=\"form-group\">\n              <label for=\"geRole\">Responsible Role</label>\n              <input type=\"text\" id=\"geRole\" class=\"form-input\" placeholder=\"e.g. Safety Systems Engineer\">\n            </div>\n            <div class=\"form-group\">\n              <label for=\"geEffectiveness\">Effectiveness</label>\n              <select id=\"geEffectiveness\" class=\"form-select\">\n                <option value=\"High\">High</option>\n                <option value=\"Medium\">Medium</option>\n                <option value=\"Low\">Low</option>\n              </select>\n            </div>\n            <div class=\"form-group\">\n              <label for=\"geDesc\">Description / Specification</label>\n              <textarea id=\"geDesc\" class=\"form-textarea\" rows=\"2\" placeholder=\"Standard specifications, ratings, SCE tags...\"></textarea>\n            </div>\n            <button type=\"submit\" class=\"btn btn-primary w-full\">Add to Master Catalog</button>\n          </form>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 8: Create New Folder -->\n    <div class=\"modal-overlay\" id=\"modalNewFolder\">\n      <div class=\"modal-box modal-sm\">\n        <div class=\"modal-header\">\n          <h3>Create New Folder</h3>\n          <button class=\"modal-close\" data-close=\"modalNewFolder\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <form id=\"newFolderForm\" novalidate>\n            <p class=\"text-muted text-xs mb-3\" id=\"newFolderParentLabel\">Inside: Root Drive</p>\n            <div class=\"form-group\">\n              <label for=\"folderNameInput\">Folder Name</label>\n              <input type=\"text\" id=\"folderNameInput\" class=\"form-input\" placeholder=\"e.g. Offshore Facilities\" autofocus>\n            </div>\n            <div class=\"form-group\">\n              <label for=\"folderColorInput\">Folder Color Tag</label>\n              <select id=\"folderColorInput\" class=\"form-select\">\n                <option value=\"#2563eb\">🔵 Blue (Standard Operations)</option>\n                <option value=\"#059669\">🟢 Green (Environmental & Safety)</option>\n                <option value=\"#d97706\">🟡 Amber (Process & Production)</option>\n                <option value=\"#dc2626\">🔴 Red (Critical High Risk)</option>\n                <option value=\"#7c3aed\">🟣 Purple (R&D / Design)</option>\n                <option value=\"#475569\">⚪ Gray (Archive / General)</option>\n              </select>\n            </div>\n            <button type=\"submit\" class=\"btn btn-primary w-full\">Create Folder</button>\n          </form>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 9: Move Assessment to Folder -->\n    <div class=\"modal-overlay\" id=\"modalMoveDiagram\">\n      <div class=\"modal-box modal-sm\">\n        <div class=\"modal-header\">\n          <h3>Move Assessment</h3>\n          <button class=\"modal-close\" data-close=\"modalMoveDiagram\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <form id=\"moveDiagramForm\" novalidate>\n            <input type=\"hidden\" id=\"moveDiagramIdInput\">\n            <p class=\"text-sm mb-3\">Move <strong id=\"moveDiagramTitleLabel\">Diagram</strong> to destination:</p>\n            <div class=\"form-group\">\n              <label for=\"moveTargetFolderSelect\">Target Folder</label>\n              <select id=\"moveTargetFolderSelect\" class=\"form-select\">\n                <!-- Dynamically populated -->\n              </select>\n            </div>\n            <button type=\"submit\" class=\"btn btn-primary w-full\">Move Assessment</button>\n          </form>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 10: Financial Cost vs Protection Level Analysis -->\n    <div class=\"modal-overlay\" id=\"modalCostAnalysis\">\n      <div class=\"modal-box modal-lg\" style=\"max-width: 920px;\">\n        <div class=\"modal-header\">\n          <div class=\"flex items-center gap-2\">\n            <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2563eb\" stroke-width=\"2\"><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"23\"/><path d=\"M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6\"/></svg>\n            <h3 style=\"margin: 0;\">Cost vs. Protection Level Analysis</h3>\n          </div>\n          <button class=\"modal-close\" data-close=\"modalCostAnalysis\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <p class=\"text-muted text-sm mb-3\">\n            Financial evaluation of protective safety barriers and mitigations against threat propagation and hazard consequences.\n          </p>\n\n          <!-- Executive Summary Metrics Cards -->\n          <div class=\"cost-summary-cards-grid\">\n            <div class=\"cost-stat-card\">\n              <span class=\"cost-stat-label\">Total Defensive Investment</span>\n              <span class=\"cost-stat-val text-primary\" id=\"costStatTotal\">$0</span>\n              <span class=\"cost-stat-sub\" id=\"costStatElementCount\">0 barriers & mitigations</span>\n            </div>\n            <div class=\"cost-stat-card\">\n              <span class=\"cost-stat-label\">Prevention Barriers (Threat)</span>\n              <span class=\"cost-stat-val text-accent\" id=\"costStatPrevention\">$0</span>\n              <span class=\"cost-stat-sub\" id=\"costStatPrevCount\">0 threat barriers</span>\n            </div>\n            <div class=\"cost-stat-card\">\n              <span class=\"cost-stat-label\">Mitigation Barriers (Outcome)</span>\n              <span class=\"cost-stat-val text-warning\" id=\"costStatMitigation\">$0</span>\n              <span class=\"cost-stat-sub\" id=\"costStatMitCount\">0 outcome barriers & mit</span>\n            </div>\n            <div class=\"cost-stat-card\">\n              <span class=\"cost-stat-label\">Safety Critical Protection</span>\n              <span class=\"cost-stat-val text-success\" id=\"costStatSceCoverage\">0%</span>\n              <span class=\"cost-stat-sub\" id=\"costStatSceCount\">0 SCE barriers active</span>\n            </div>\n          </div>\n\n          <!-- Cost vs Protection Detailed Matrix Table -->\n          <div class=\"cost-table-container mt-4\">\n            <table class=\"cost-analysis-table\">\n              <thead>\n                <tr>\n                  <th>Element</th>\n                  <th>Type</th>\n                  <th>Protection Level</th>\n                  <th>Criticality / SCE</th>\n                  <th>Cost Estimate</th>\n                  <th>Efficiency Assessment</th>\n                </tr>\n              </thead>\n              <tbody id=\"costAnalysisTableBody\">\n                <!-- Dynamically populated -->\n              </tbody>\n            </table>\n          </div>\n        </div>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"btn btn-outline\" id=\"btnExportCostCsv\">Export Cost Summary</button>\n          <div class=\"flex-spacer\"></div>\n          <button type=\"button\" class=\"btn btn-secondary\" data-close=\"modalCostAnalysis\">Close</button>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 11: Enterprise Admin Portal placeholder for backwards compatibility -->\n    <div id=\"modalAdminPortal\" style=\"display: none;\"></div>\n\n    <!-- MODAL 12: Admin Direct User Password Reset -->\n    <div class=\"modal-overlay\" id=\"modalAdminResetPassword\">\n      <div class=\"modal-box modal-sm\">\n        <div class=\"modal-header\">\n          <h3>Reset User Password</h3>\n          <button class=\"modal-close\" data-close=\"modalAdminResetPassword\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <form id=\"adminResetPasswordForm\" novalidate>\n            <input type=\"hidden\" id=\"adminResetTargetUserId\">\n            <p class=\"text-xs text-muted mb-3\">\n              Directly set a new password for <strong id=\"adminResetTargetEmail\">user@example.com</strong>. Active sessions for this user will be revoked immediately.\n            </p>\n            <div id=\"adminResetPasswordAlert\" class=\"auth-wall-error\" style=\"display: none; margin-bottom: 10px;\"></div>\n            <div class=\"form-group\">\n              <label for=\"adminResetNewPassword\">New Password</label>\n              <input type=\"password\" id=\"adminResetNewPassword\" class=\"form-input\" placeholder=\"Min. 4 characters\" autofocus>\n            </div>\n            <div class=\"form-group\">\n              <label for=\"adminResetConfirmPassword\">Confirm Password</label>\n              <input type=\"password\" id=\"adminResetConfirmPassword\" class=\"form-input\" placeholder=\"Confirm new password\">\n            </div>\n            <button type=\"submit\" class=\"btn btn-primary w-full\" id=\"btnSubmitAdminResetPassword\">Apply New Password</button>\n          </form>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 13: My Profile & Avatar -->\n    <div class=\"modal-overlay\" id=\"modalMyProfile\">\n      <div class=\"modal-box modal-md\" style=\"max-width: 520px;\">\n        <div class=\"modal-header\">\n          <div class=\"flex items-center gap-2\">\n            <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2563eb\" stroke-width=\"2\"><path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></svg>\n            <h3 style=\"margin: 0;\">My Profile & Avatar</h3>\n          </div>\n          <button class=\"modal-close\" data-close=\"modalMyProfile\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <div id=\"myProfileAlert\" class=\"auth-wall-error\" style=\"display: none; margin-bottom: 12px;\"></div>\n          <div id=\"myProfileNotice\" class=\"auth-wall-success\" style=\"display: none; margin-bottom: 12px;\"></div>\n\n          <!-- Avatar Section -->\n          <div class=\"profile-avatar-editor flex items-center gap-4 mb-4\" style=\"background: #f8fafc; padding: 14px; border-radius: 8px; border: 1px solid #e2e8f0;\">\n            <div id=\"myProfileAvatarPreview\" class=\"avatar-lg\" style=\"width: 72px; height: 72px; font-size: 28px; flex-shrink: 0; box-shadow: 0 2px 6px rgba(0,0,0,0.08); border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #2563eb; color: #fff; overflow: hidden;\">\n              <!-- Rendered via JS -->\n            </div>\n            <div style=\"flex: 1;\">\n              <div class=\"text-xs font-semibold mb-1\" style=\"color: #334155;\">Profile Picture / Icon</div>\n              <div class=\"flex items-center gap-2 mb-2\" style=\"flex-wrap: wrap;\">\n                <label class=\"btn btn-outline btn-sm\" style=\"font-size: 11px; cursor: pointer; margin: 0;\">\n                  <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/></svg>\n                  Upload Photo\n                  <input type=\"file\" id=\"myProfileAvatarFileInput\" accept=\"image/*\" style=\"display: none;\">\n                </label>\n                <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnMyProfileClearAvatar\" style=\"font-size: 11px; color: #64748b;\">\n                  Reset\n                </button>\n              </div>\n              <div class=\"text-xs text-muted\">Or pick an icon badge:</div>\n              <div class=\"avatar-preset-picker flex items-center gap-1 mt-1\" id=\"myProfilePresetIcons\" style=\"flex-wrap: wrap;\">\n                <!-- Emojis & icons: 👤, 👷, 🛡️, ⚙️, 🔬, ⚡, 🏢, ⚓, 🚒, 🎯, 🚀, 💻, 🧠, 📊 -->\n              </div>\n            </div>\n          </div>\n\n          <form id=\"myProfileForm\" novalidate>\n            <div class=\"form-group\">\n              <label for=\"myProfileNameInput\">Full Name *</label>\n              <input type=\"text\" id=\"myProfileNameInput\" class=\"form-input\" placeholder=\"e.g. Jane Doe\" required>\n            </div>\n            <div class=\"form-group\">\n              <label for=\"myProfileEmailDisplay\">Email Address</label>\n              <input type=\"email\" id=\"myProfileEmailDisplay\" class=\"form-input\" style=\"background: #f1f5f9; cursor: not-allowed;\" readonly>\n              <small class=\"text-xs text-muted\">Primary account email managed by system administrator.</small>\n            </div>\n            <div class=\"form-row flex gap-2\">\n              <div class=\"form-group\" style=\"flex: 1;\">\n                <label for=\"myProfileRoleInput\">Job Title / Role</label>\n                <input type=\"text\" id=\"myProfileRoleInput\" class=\"form-input\" placeholder=\"e.g. Lead Risk Assessor\">\n              </div>\n              <div class=\"form-group\" style=\"flex: 1;\">\n                <label for=\"myProfileOrgInput\">Organization</label>\n                <input type=\"text\" id=\"myProfileOrgInput\" class=\"form-input\" placeholder=\"e.g. Offshore Facilities Ltd\">\n              </div>\n            </div>\n            <div class=\"form-group\">\n              <label for=\"myProfilePhoneInput\">Phone Number</label>\n              <input type=\"tel\" id=\"myProfilePhoneInput\" class=\"form-input\" placeholder=\"e.g. +44 7700 900123\">\n            </div>\n            <div class=\"modal-footer\" style=\"padding-top: 14px; margin-top: 10px;\">\n              <button type=\"button\" class=\"btn btn-secondary\" data-close=\"modalMyProfile\">Cancel</button>\n              <button type=\"submit\" class=\"btn btn-primary\" id=\"btnSaveMyProfile\">Save Changes</button>\n            </div>\n          </form>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 14: Admin Edit User Profile -->\n    <div class=\"modal-overlay\" id=\"modalAdminEditUser\">\n      <div class=\"modal-box modal-md\" style=\"max-width: 580px;\">\n        <div class=\"modal-header\">\n          <div class=\"flex items-center gap-2\">\n            <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#7c3aed\" stroke-width=\"2\"><path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/><path d=\"M16 11l2 2 4-4\"/></svg>\n            <h3 style=\"margin: 0;\">Edit User Profile & Settings</h3>\n          </div>\n          <button class=\"modal-close\" data-close=\"modalAdminEditUser\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <input type=\"hidden\" id=\"adminEditUserId\">\n          <div id=\"adminEditUserAlert\" class=\"auth-wall-error\" style=\"display: none; margin-bottom: 12px;\"></div>\n          <div id=\"adminEditUserNotice\" class=\"auth-wall-success\" style=\"display: none; margin-bottom: 12px;\"></div>\n\n          <!-- Avatar Editor -->\n          <div class=\"flex items-center gap-3 mb-4\" style=\"background: #f8fafc; padding: 12px; border-radius: 8px; border: 1px solid #e2e8f0;\">\n            <div id=\"adminEditAvatarPreview\" class=\"avatar-lg\" style=\"width: 60px; height: 60px; font-size: 24px; flex-shrink: 0; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #7c3aed; color: #fff; overflow: hidden;\">\n            </div>\n            <div style=\"flex: 1;\">\n              <div class=\"text-xs font-semibold mb-1\" style=\"color: #334155;\">User Avatar & Icon</div>\n              <div class=\"flex items-center gap-2 mb-1\" style=\"flex-wrap: wrap;\">\n                <label class=\"btn btn-outline btn-sm\" style=\"font-size: 11px; cursor: pointer; margin: 0;\">\n                  Upload Photo\n                  <input type=\"file\" id=\"adminEditAvatarFileInput\" accept=\"image/*\" style=\"display: none;\">\n                </label>\n                <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnAdminEditClearAvatar\" style=\"font-size: 11px; color: #64748b;\">\n                  Reset\n                </button>\n              </div>\n              <div class=\"avatar-preset-picker flex items-center gap-1\" id=\"adminEditPresetIcons\" style=\"flex-wrap: wrap;\"></div>\n            </div>\n          </div>\n\n          <form id=\"adminEditUserForm\" novalidate>\n            <div class=\"form-row flex gap-2\">\n              <div class=\"form-group\" style=\"flex: 1;\">\n                <label for=\"adminEditUserName\">Full Name *</label>\n                <input type=\"text\" id=\"adminEditUserName\" class=\"form-input\" required>\n              </div>\n              <div class=\"form-group\" style=\"flex: 1;\">\n                <label for=\"adminEditUserEmail\">Email Address *</label>\n                <input type=\"email\" id=\"adminEditUserEmail\" class=\"form-input\" required>\n              </div>\n            </div>\n            <div class=\"form-row flex gap-2\">\n              <div class=\"form-group\" style=\"flex: 1;\">\n                <label for=\"adminEditUserRole\">Role / Job Title</label>\n                <input type=\"text\" id=\"adminEditUserRole\" class=\"form-input\" placeholder=\"e.g. Risk Assessor\">\n              </div>\n              <div class=\"form-group\" style=\"flex: 1;\">\n                <label for=\"adminEditUserOrg\">Organization</label>\n                <input type=\"text\" id=\"adminEditUserOrg\" class=\"form-input\" placeholder=\"e.g. Operations\">\n              </div>\n            </div>\n            <div class=\"form-group\">\n              <label for=\"adminEditUserPhone\">Phone Number</label>\n              <input type=\"tel\" id=\"adminEditUserPhone\" class=\"form-input\" placeholder=\"e.g. +44 7700 900123\">\n            </div>\n\n            <div class=\"admin-grid-2 mb-3\" style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 10px; padding: 12px; background: #f8fafc; border-radius: 8px; border: 1px solid #e2e8f0;\">\n              <div>\n                <label class=\"text-xs font-semibold\" style=\"display: block; margin-bottom: 6px; color: #334155;\">Privilege Level</label>\n                <select id=\"adminEditUserPrivilege\" class=\"form-select text-sm\">\n                  <option value=\"user\">Standard User</option>\n                  <option value=\"admin\">Administrator (Full Access)</option>\n                </select>\n              </div>\n              <div>\n                <label class=\"text-xs font-semibold\" style=\"display: block; margin-bottom: 6px; color: #334155;\">Account Status</label>\n                <select id=\"adminEditUserStatus\" class=\"form-select text-sm\">\n                  <option value=\"active\">Active</option>\n                  <option value=\"disabled\">Disabled (Blocked / No Access)</option>\n                </select>\n              </div>\n            </div>\n\n            <!-- Danger Zone: Password Reset & Deletion -->\n            <div class=\"admin-danger-zone mt-3 mb-3\" style=\"padding: 12px; border: 1px solid #fecaca; background: #fef2f2; border-radius: 8px;\">\n              <div class=\"text-xs font-semibold text-danger mb-2\">Account Actions & Security</div>\n              <div class=\"flex items-center gap-2\" style=\"flex-wrap: wrap;\">\n                <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnAdminEditTriggerResetPw\" style=\"font-size: 11px;\">\n                  Reset Password Directly\n                </button>\n                <button type=\"button\" class=\"btn btn-danger btn-sm\" id=\"btnAdminEditDeleteUser\" style=\"font-size: 11px; margin-left: auto;\">\n                  <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg>\n                  Delete User Account\n                </button>\n              </div>\n            </div>\n\n            <div class=\"modal-footer\" style=\"padding-top: 12px; margin-top: 8px;\">\n              <button type=\"button\" class=\"btn btn-secondary\" data-close=\"modalAdminEditUser\">Cancel</button>\n              <button type=\"submit\" class=\"btn btn-primary\" id=\"btnAdminSaveUser\">Save Profile Changes</button>\n            </div>\n          </form>\n        </div>\n      </div>\n    </div>\n\n    <!-- MODAL 15: Admin CSV User Import -->\n    <div class=\"modal-overlay\" id=\"modalAdminImportUsers\">\n      <div class=\"modal-box modal-lg\" style=\"max-width: 680px;\">\n        <div class=\"modal-header\">\n          <div class=\"flex items-center gap-2\">\n            <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2563eb\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/></svg>\n            <h3 style=\"margin: 0;\">Import Users from CSV</h3>\n          </div>\n          <button class=\"modal-close\" data-close=\"modalAdminImportUsers\">&times;</button>\n        </div>\n        <div class=\"modal-body\">\n          <p class=\"text-xs text-muted mb-3\">\n            Bulk-import or update user accounts via standard CSV. Existing accounts matching by email will have their profiles updated; new accounts will be registered.\n          </p>\n\n          <div class=\"flex items-center justify-between mb-3\" style=\"background: #f8fafc; padding: 10px 14px; border-radius: 6px; border: 1px solid #e2e8f0; flex-wrap: wrap; gap: 8px;\">\n            <div class=\"text-xs\">\n              <strong>Supported Columns:</strong> <code>email</code> (required), <code>name</code>, <code>role</code>, <code>organization</code>, <code>phone</code>, <code>is_admin</code>, <code>is_disabled</code>, <code>avatar_url</code>\n            </div>\n            <button type=\"button\" class=\"btn btn-outline btn-sm\" id=\"btnDownloadCsvTemplate\" style=\"font-size: 11px; white-space: nowrap;\">\n              <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>\n              Download Template\n            </button>\n          </div>\n\n          <div id=\"adminImportAlert\" class=\"auth-wall-error\" style=\"display: none; margin-bottom: 10px;\"></div>\n          <div id=\"adminImportNotice\" class=\"auth-wall-success\" style=\"display: none; margin-bottom: 10px;\"></div>\n\n          <div class=\"form-group mb-3\">\n            <label class=\"text-xs font-semibold mb-1\" style=\"display: block;\">Option A: Upload CSV File</label>\n            <input type=\"file\" id=\"adminImportCsvFileInput\" accept=\".csv,text/csv\" class=\"form-input text-sm\">\n          </div>\n\n          <div class=\"form-group mb-3\">\n            <label for=\"adminImportCsvTextArea\" class=\"text-xs font-semibold mb-1\" style=\"display: block;\">Option B: Or Paste CSV Content</label>\n            <textarea id=\"adminImportCsvTextArea\" class=\"form-input text-xs\" rows=\"8\" placeholder=\"email,name,role,organization,phone,is_admin,is_disabled,avatar_url&#10;jane@company.com,Jane Doe,Safety Lead,Offshore,+1-555-0101,false,false,👷\" style=\"font-family: monospace;\"></textarea>\n          </div>\n\n          <div id=\"adminImportResultsBox\" style=\"display: none; background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 6px; padding: 12px; margin-bottom: 12px;\">\n            <div class=\"text-xs font-semibold\" style=\"color: #166534;\" id=\"adminImportResultsSummary\"></div>\n            <div id=\"adminImportResultsErrors\" class=\"text-xs text-danger mt-1\"></div>\n          </div>\n\n          <div class=\"modal-footer\" style=\"padding-top: 12px;\">\n            <button type=\"button\" class=\"btn btn-secondary\" data-close=\"modalAdminImportUsers\">Cancel</button>\n            <button type=\"button\" class=\"btn btn-primary\" id=\"btnSubmitAdminImportCsv\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/></svg>\n              Execute CSV Import\n            </button>\n          </div>\n    <!-- MODAL: Export PNG Preview (for Mobile & Quick Sharing) -->\n    <div class=\"modal-overlay\" id=\"modalExportPng\">\n      <div class=\"modal-box\" style=\"max-width: 520px;\">\n        <div class=\"modal-header\">\n          <div class=\"flex items-center gap-2\">\n            <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2563eb\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>\n            <h3 style=\"margin: 0; font-size: 16px;\">Diagram Image Export</h3>\n          </div>\n          <button class=\"modal-close\" data-close=\"modalExportPng\">&times;</button>\n        </div>\n        <div class=\"modal-body\" style=\"padding: 16px; text-align: center;\">\n          <p class=\"text-muted text-sm mb-3\">\n            Press and hold the image to save directly to your Photos or device files.\n          </p>\n          <div style=\"max-height: 55vh; overflow: auto; border: 1px solid var(--border-light, #e2e8f0); border-radius: 8px; padding: 6px; background: #f8fafc; display: flex; align-items: center; justify-content: center;\">\n            <img id=\"exportPngPreviewImg\" src=\"\" alt=\"Bowtie Diagram\" style=\"max-width: 100%; height: auto; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.08);\" />\n          </div>\n        </div>\n        <div class=\"modal-footer\" style=\"display: flex; gap: 8px; justify-content: flex-end; padding: 12px 16px;\">\n          <button type=\"button\" class=\"btn btn-secondary\" data-close=\"modalExportPng\">Close</button>\n          <a id=\"btnExportPngDownloadLink\" class=\"btn btn-primary\" download=\"\" href=\"#\" style=\"text-decoration: none; display: inline-flex; align-items: center; gap: 6px;\">\n            <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>\n            Download Image\n          </a>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <!-- Scripts -->\n  <script src=\"/js/layout.js?v=1.1.10\"></script>\n  <script src=\"/js/canvas.js?v=1.1.10\"></script>\n  <script src=\"/js/tree.js?v=1.1.10\"></script>\n  <script src=\"/js/inspector.js?v=1.1.10\"></script>\n  <script src=\"/js/export.js?v=1.1.10\"></script>\n  <script src=\"/js/documents.js?v=1.1.10\"></script>\n  <script src=\"/js/simulator.js?v=1.1.10\"></script>\n  <script src=\"/js/app.js?v=1.1.10\"></script>\n</body>\n</html>\n",
    "js/app.js": "// Main Controller & Application Orchestration\nclass BowtieApp {\n  constructor() {\n    this.workspace = null;\n    this.currentUser = null;\n    this.currentDiagram = null;\n    this.autoSaveTimer = null;\n    this.isSaving = false;\n\n    // Security & Auth State\n    this.authToken = localStorage.getItem('bowtie_auth_token') || null;\n\n    // Real-time Multi-User Collaboration State (Google Docs Style)\n    this.sessionId = 'sess-' + Math.random().toString(36).substring(2, 10);\n    this.userColor = this.assignUserColor();\n    this.realtimeSource = null;\n    this.collaborators = new Map(); // sessionId -> CollaboratorPresence\n    this.lastPresenceSent = 0;\n\n    // Sub-components\n    this.canvas = null;\n    this.tree = null;\n    this.inspector = null;\n    this.exporter = null;\n    this.documents = null;\n    this.currentView = 'canvas';\n    this.openDiagramIds = [];\n\n    // In-Memory Undo / Redo History Stacks (Isolated per diagram)\n    this.undoStacks = {};\n    this.redoStacks = {};\n    this.maxUndoStack = 50;\n    this.isPerformingUndoRedo = false;\n    this.autoAlignEnabled = localStorage.getItem('bowtie_auto_align_enabled') === 'true';\n    this.elementSuggestions = null;\n  }\n\n  get undoStack() {\n    return this.getUndoStack();\n  }\n  set undoStack(val) {\n    const diagId = this.currentDiagram ? this.currentDiagram.id : null;\n    if (diagId) this.undoStacks[diagId] = val;\n  }\n\n  get redoStack() {\n    return this.getRedoStack();\n  }\n  set redoStack(val) {\n    const diagId = this.currentDiagram ? this.currentDiagram.id : null;\n    if (diagId) this.redoStacks[diagId] = val;\n  }\n\n  assignUserColor() {\n    const palette = ['#2563eb', '#10b981', '#f59e0b', '#ec4899', '#8b5cf6', '#06b6d4', '#e11d48'];\n    const idx = Math.floor(Math.random() * palette.length);\n    return palette[idx];\n  }\n\n  apiFetch(url, options = {}) {\n    options.headers = options.headers || {};\n    if (this.authToken) {\n      if (options.headers instanceof Headers) {\n        options.headers.set('Authorization', `Bearer ${this.authToken}`);\n      } else {\n        options.headers['Authorization'] = `Bearer ${this.authToken}`;\n      }\n    }\n    return fetch(url, options);\n  }\n\n  async init() {\n    this.initDOM();\n    this.initAuthWallEvents();\n    const authed = await this.loadInitialData();\n    if (!authed) {\n      this.showAuthWall();\n      return;\n    }\n    this.hideAuthWall();\n    this.initSubComponents();\n    this.initModals();\n    this.initToolbarButtons();\n    this.switchToView('drive');\n    console.log(\"Bowtie RISK assessment app initialized successfully.\");\n  }\n\n  initDOM() {\n    this.viewportEl = document.getElementById('canvasViewport');\n    this.stageEl = document.getElementById('canvasStage');\n    this.connectionsGroupEl = document.getElementById('connectionsGroup');\n    this.nodesLayerEl = document.getElementById('nodesLayer');\n    this.treeContainerEl = document.getElementById('treeContainer');\n    this.libraryContainerEl = document.getElementById('globalElementsList');\n  }\n\n  async loadInitialData() {\n    try {\n      if (!this.authToken) {\n        return false;\n      }\n\n      // 1. Current user\n      const userRes = await this.apiFetch('/api/auth/me');\n      if (!userRes.ok) {\n        return false;\n      }\n      this.currentUser = await userRes.json();\n      if (!this.currentUser || !this.currentUser.email || this.currentUser.id === 'guest') {\n        return false;\n      }\n\n      // 2. Full workspace\n      const wsRes = await this.apiFetch('/api/workspace');\n      if (!wsRes.ok) {\n        return false;\n      }\n      this.workspace = await wsRes.json();\n      if (this.workspace && this.workspace.element_suggestions) {\n        this.elementSuggestions = JSON.parse(JSON.stringify(this.workspace.element_suggestions));\n      } else {\n        this.elementSuggestions = this.getDefaultElementSuggestions();\n      }\n\n      // Restore saved open diagram IDs if any exist in localStorage\n      const diagrams = (this.workspace && this.workspace.diagrams) || [];\n      const savedOpenIdsStr = localStorage.getItem('bowtie_open_diagram_ids');\n      let restoredIds = [];\n      if (savedOpenIdsStr) {\n        try {\n          const parsed = JSON.parse(savedOpenIdsStr);\n          if (Array.isArray(parsed)) {\n            restoredIds = parsed.filter(id => {\n              if (id === 'admin') {\n                return !!(this.currentUser && this.currentUser.is_admin);\n              }\n              return diagrams.some(d => d.id === id);\n            });\n          }\n        } catch (_) {}\n      }\n      this.openDiagramIds = restoredIds;\n      const lastDiagId = [...this.openDiagramIds].reverse().find(id => id !== 'admin');\n      this.currentDiagram = lastDiagId\n        ? (diagrams.find(d => d.id === lastDiagId) || diagrams[0] || null)\n        : (diagrams[0] || null);\n\n      // Populate directory users map from current user and workspace users for avatar resolution\n      if (!this.directoryUsersMap) this.directoryUsersMap = {};\n      if (this.currentUser && this.currentUser.email) {\n        this.directoryUsersMap[this.currentUser.email.toLowerCase()] = this.currentUser;\n      }\n      if (this.workspace && this.workspace.users) {\n        this.workspace.users.forEach(u => {\n          if (u.email) {\n            this.directoryUsersMap[u.email.toLowerCase()] = u;\n          }\n        });\n      }\n      await this.ensureDirectoryLoaded();\n\n      if (this.workspace.nodes) {\n        this.normalizeNodeDimensions();\n        this.fixInvertedOutcomeBarriers();\n      }\n\n      this.updateUserUI();\n      this.updateUnlicensedBannerUI();\n      this.initDiagramSelector();\n      this.renderDiagramTabs();\n      this.updateAuditBadge();\n      if (this.currentView === 'canvas' && this.currentDiagram) {\n        this.initRealtimeCollaboration();\n      }\n      return true;\n    } catch (err) {\n      console.error('Error loading initial data:', err);\n      return false;\n    }\n  }\n\n  initSubComponents() {\n    this.layout = new BowtieLayoutEngine(this);\n    this.canvas = new BowtieCanvas(this.viewportEl, this.stageEl, this.connectionsGroupEl, this.nodesLayerEl, this);\n    this.tree = new BowtieTree(this.treeContainerEl, this.libraryContainerEl, this);\n    this.inspector = new BowtieInspector(this);\n    this.exporter = new BowtieExporter(this);\n    this.documents = new BowtieDocumentsDrive(this);\n    this.situationRunner = new BowtieSimulator(this);\n\n    this.normalizeNodeDimensions();\n    this.fixInvertedOutcomeBarriers();\n\n    // Initial render\n    this.canvas.render();\n    this.tree.render();\n    this.documents.render();\n\n    // Select first node if available on current diagram\n    if (this.currentDiagram && this.workspace.nodes && this.workspace.nodes.length > 0) {\n      const diagNodes = this.workspace.nodes.filter(n => n.diagram_id === this.currentDiagram.id);\n      const topEvent = diagNodes.find(n => n.node_type === 'top_event');\n      if (topEvent) {\n        this.canvas.setActiveNode(topEvent.id);\n      }\n    }\n  }\n\n  showAuthWall(errorMsg = null) {\n    const wall = document.getElementById('authWallScreen');\n    const app = document.getElementById('app');\n    if (app) app.style.display = 'none';\n    if (wall) {\n      wall.style.display = 'flex';\n      if (typeof this.showLoginView === 'function') {\n        this.showLoginView();\n      }\n      const formSignup = document.getElementById('wallSignupForm');\n      if (formSignup) formSignup.reset();\n      const loginPassword = document.getElementById('wallLoginPassword');\n      if (loginPassword) loginPassword.value = '';\n\n      const errBox = document.getElementById('authWallError');\n      if (errBox) {\n        if (errorMsg) {\n          errBox.textContent = errorMsg;\n          errBox.style.display = 'block';\n        } else {\n          errBox.style.display = 'none';\n        }\n      }\n    }\n  }\n\n  hideAuthWall() {\n    const wall = document.getElementById('authWallScreen');\n    const app = document.getElementById('app');\n    if (wall) wall.style.display = 'none';\n    if (app) app.style.display = 'flex';\n  }\n\n  initAuthWallEvents() {\n    const tabsContainer = document.querySelector('.auth-wall-tabs');\n    const tabLogin = document.getElementById('wallTabLoginBtn');\n    const tabSignup = document.getElementById('wallTabSignupBtn');\n    const formLogin = document.getElementById('wallLoginForm');\n    const formSignup = document.getElementById('wallSignupForm');\n    const formForgot = document.getElementById('wallForgotForm');\n    const formReset = document.getElementById('wallResetForm');\n    const errBox = document.getElementById('authWallError');\n    const successBox = document.getElementById('authWallSuccess');\n\n    const clearMessages = () => {\n      if (errBox) { errBox.style.display = 'none'; errBox.textContent = ''; }\n      if (successBox) { successBox.style.display = 'none'; successBox.textContent = ''; }\n    };\n\n    const showLoginView = () => {\n      clearMessages();\n      if (tabsContainer) tabsContainer.style.display = 'flex';\n      if (tabLogin) tabLogin.classList.add('active');\n      if (tabSignup) tabSignup.classList.remove('active');\n      if (formLogin) formLogin.style.display = 'block';\n      if (formSignup) formSignup.style.display = 'none';\n      if (formForgot) formForgot.style.display = 'none';\n      if (formReset) formReset.style.display = 'none';\n    };\n\n    const showSignupView = () => {\n      clearMessages();\n      if (tabsContainer) tabsContainer.style.display = 'flex';\n      if (tabSignup) tabSignup.classList.add('active');\n      if (tabLogin) tabLogin.classList.remove('active');\n      if (formSignup) formSignup.style.display = 'block';\n      if (formLogin) formLogin.style.display = 'none';\n      if (formForgot) formForgot.style.display = 'none';\n      if (formReset) formReset.style.display = 'none';\n    };\n\n    const showForgotView = (emailVal = '') => {\n      clearMessages();\n      if (tabsContainer) tabsContainer.style.display = 'none';\n      if (formLogin) formLogin.style.display = 'none';\n      if (formSignup) formSignup.style.display = 'none';\n      if (formForgot) formForgot.style.display = 'block';\n      if (formReset) formReset.style.display = 'none';\n      const forgotEmailInput = document.getElementById('wallForgotEmail');\n      if (forgotEmailInput) {\n        if (emailVal) forgotEmailInput.value = emailVal;\n        forgotEmailInput.focus();\n      }\n    };\n\n    const showResetView = (emailVal = '', initialMessage = '') => {\n      clearMessages();\n      if (tabsContainer) tabsContainer.style.display = 'none';\n      if (formLogin) formLogin.style.display = 'none';\n      if (formSignup) formSignup.style.display = 'none';\n      if (formForgot) formForgot.style.display = 'none';\n      if (formReset) formReset.style.display = 'block';\n      const resetEmailInput = document.getElementById('wallResetEmail');\n      if (resetEmailInput) resetEmailInput.value = emailVal;\n      if (initialMessage && successBox) {\n        successBox.textContent = initialMessage;\n        successBox.style.display = 'block';\n      }\n      const codeInput = document.getElementById('wallResetCode');\n      if (codeInput) {\n        codeInput.value = '';\n        codeInput.focus();\n      }\n    };\n\n    this.showLoginView = showLoginView;\n    this.showSignupView = showSignupView;\n\n    if (tabLogin) tabLogin.onclick = showLoginView;\n    if (tabSignup) tabSignup.onclick = showSignupView;\n\n    const linkForgot = document.getElementById('linkWallForgotPassword');\n    if (linkForgot) {\n      linkForgot.onclick = (e) => {\n        e.preventDefault();\n        const currentEmail = (document.getElementById('wallLoginEmail')?.value || '').trim();\n        showForgotView(currentEmail);\n      };\n    }\n\n    const linkBackFromForgot = document.getElementById('linkWallBackToLoginFromForgot');\n    if (linkBackFromForgot) {\n      linkBackFromForgot.onclick = (e) => { e.preventDefault(); showLoginView(); };\n    }\n\n    const linkBackFromReset = document.getElementById('linkWallBackToLoginFromReset');\n    if (linkBackFromReset) {\n      linkBackFromReset.onclick = (e) => { e.preventDefault(); showLoginView(); };\n    }\n\n    const linkResend = document.getElementById('linkWallResendCode');\n    if (linkResend) {\n      linkResend.onclick = (e) => {\n        e.preventDefault();\n        const email = (document.getElementById('wallResetEmail')?.value || '').trim();\n        showForgotView(email);\n      };\n    }\n\n    // 1. Sign In\n    if (formLogin) {\n      formLogin.onsubmit = async (e) => {\n        e.preventDefault();\n        const email = document.getElementById('wallLoginEmail').value.trim();\n        const password = document.getElementById('wallLoginPassword').value;\n        const btn = document.getElementById('btnWallSubmitLogin');\n        if (btn) btn.disabled = true;\n        clearMessages();\n\n        try {\n          const res = await fetch('/api/auth/login', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ email, password })\n          });\n          let data = {};\n          try {\n            data = await res.json();\n          } catch (_) {\n            const txt = await res.text().catch(() => '');\n            data = { message: txt, error: txt };\n          }\n\n          if (res.ok) {\n            const token = data.token || data.session_token || null;\n            if (token) {\n              this.authToken = token;\n              localStorage.setItem('bowtie_auth_token', token);\n            }\n            if (data.user) {\n              this.currentUser = data.user;\n            }\n            const loginPassword = document.getElementById('wallLoginPassword');\n            if (loginPassword) loginPassword.value = '';\n            clearMessages();\n            this.hideAuthWall();\n            localStorage.removeItem('bowtie_open_diagram_ids');\n            this.openDiagramIds = [];\n            this.currentDiagram = null;\n            await this.loadInitialData();\n            if (!this.canvas) {\n              this.initSubComponents();\n              this.initModals();\n              this.initToolbarButtons();\n            } else {\n              this.canvas.render();\n              this.tree.render();\n              if (this.documents) this.documents.render();\n            }\n            this.switchToView('drive');\n            this.showNotification(`Signed in successfully as ${email}`, 'success');\n          } else {\n            if (errBox) {\n              const msg = data.message || data.error || 'Invalid email or password.';\n              errBox.textContent = msg;\n              errBox.style.display = 'block';\n            }\n          }\n        } catch (err) {\n          if (errBox) {\n            errBox.textContent = 'Server connection failed. Please ensure backend is reachable.';\n            errBox.style.display = 'block';\n          }\n        } finally {\n          if (btn) btn.disabled = false;\n        }\n      };\n    }\n\n    // 2. Sign Up\n    if (formSignup) {\n      formSignup.onsubmit = async (e) => {\n        e.preventDefault();\n        const name = document.getElementById('wallSignupName').value.trim();\n        const email = document.getElementById('wallSignupEmail').value.trim();\n        const password = document.getElementById('wallSignupPassword').value;\n        const organization = (document.getElementById('wallSignupOrg').value || '').trim();\n        const btn = document.getElementById('btnWallSubmitSignup');\n        if (btn) btn.disabled = true;\n        clearMessages();\n\n        try {\n          const res = await fetch('/api/auth/signup', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ name, email, password, organization, role: 'Risk Assessor' })\n          });\n          let data = {};\n          try {\n            data = await res.json();\n          } catch (_) {\n            const txt = await res.text().catch(() => '');\n            data = { message: txt, error: txt };\n          }\n\n          if (res.ok) {\n            const token = data.token || data.session_token || null;\n            if (token) {\n              this.authToken = token;\n              localStorage.setItem('bowtie_auth_token', token);\n            }\n            if (data.user) {\n              this.currentUser = data.user;\n            }\n            formSignup.reset();\n            showLoginView();\n            clearMessages();\n            this.hideAuthWall();\n            localStorage.removeItem('bowtie_open_diagram_ids');\n            this.openDiagramIds = [];\n            this.currentDiagram = null;\n            await this.loadInitialData();\n            if (!this.canvas) {\n              this.initSubComponents();\n              this.initModals();\n              this.initToolbarButtons();\n            } else {\n              this.canvas.render();\n              this.tree.render();\n              if (this.documents) this.documents.render();\n            }\n            this.switchToView('drive');\n            this.showNotification(`Account created! Welcome, ${name || email}.`, 'success');\n          } else {\n            if (errBox) {\n              const msg = data.message || data.error || 'Registration failed.';\n              errBox.textContent = msg;\n              errBox.style.display = 'block';\n            }\n          }\n        } catch (err) {\n          if (errBox) {\n            errBox.textContent = 'Server connection failed. Please try again.';\n            errBox.style.display = 'block';\n          }\n        } finally {\n          if (btn) btn.disabled = false;\n        }\n      };\n    }\n\n    // 3. Forgot Password Request (Step 1)\n    if (formForgot) {\n      formForgot.onsubmit = async (e) => {\n        e.preventDefault();\n        const email = (document.getElementById('wallForgotEmail')?.value || '').trim();\n        const btn = document.getElementById('btnWallSubmitForgot');\n        if (!email) {\n          if (errBox) { errBox.textContent = 'Please enter your account email.'; errBox.style.display = 'block'; }\n          return;\n        }\n        if (btn) btn.disabled = true;\n        clearMessages();\n\n        try {\n          const res = await fetch('/api/auth/forgot-password', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ email })\n          });\n          const data = await res.json().catch(() => ({}));\n\n          if (res.ok) {\n            showResetView(email, data.message || `Verification code generated for ${email}. Check your code below.`);\n            const codeInput = document.getElementById('wallResetCode');\n            if (codeInput && data.code) {\n              codeInput.value = data.code;\n            }\n          } else {\n            if (errBox) {\n              errBox.textContent = data.message || data.error || 'Failed to request verification code.';\n              errBox.style.display = 'block';\n            }\n          }\n        } catch (err) {\n          if (errBox) {\n            errBox.textContent = 'Network error while requesting password reset.';\n            errBox.style.display = 'block';\n          }\n        } finally {\n          if (btn) btn.disabled = false;\n        }\n      };\n    }\n\n    // 4. Reset Password Verification & Update (Step 2)\n    if (formReset) {\n      formReset.onsubmit = async (e) => {\n        e.preventDefault();\n        const email = (document.getElementById('wallResetEmail')?.value || '').trim();\n        const code = (document.getElementById('wallResetCode')?.value || '').trim();\n        const new_password = document.getElementById('wallResetNewPassword')?.value || '';\n        const confirm_password = document.getElementById('wallResetConfirmPassword')?.value || '';\n        const btn = document.getElementById('btnWallSubmitReset');\n\n        clearMessages();\n\n        if (!code || code.length !== 6) {\n          if (errBox) { errBox.textContent = 'Please enter the 6-digit verification code.'; errBox.style.display = 'block'; }\n          return;\n        }\n        if (new_password.length < 4) {\n          if (errBox) { errBox.textContent = 'New password must be at least 4 characters.'; errBox.style.display = 'block'; }\n          return;\n        }\n        if (new_password !== confirm_password) {\n          if (errBox) { errBox.textContent = 'Passwords do not match. Please re-enter.'; errBox.style.display = 'block'; }\n          return;\n        }\n\n        if (btn) btn.disabled = true;\n\n        try {\n          const res = await fetch('/api/auth/reset-password', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ email, code, new_password })\n          });\n          const data = await res.json().catch(() => ({}));\n\n          if (res.ok) {\n            const token = data.token || null;\n            if (token) {\n              this.authToken = token;\n              localStorage.setItem('bowtie_auth_token', token);\n            }\n            if (data.user) {\n              this.currentUser = data.user;\n            }\n            clearMessages();\n            this.hideAuthWall();\n            await this.loadInitialData();\n            if (!this.canvas) {\n              this.initSubComponents();\n              this.initModals();\n              this.initToolbarButtons();\n            } else {\n              this.canvas.render();\n              this.tree.render();\n              if (this.documents) this.documents.render();\n            }\n            this.switchToView('drive');\n            this.showNotification(`Password reset successfully! Welcome back, ${this.currentUser.name || email}.`, 'success');\n          } else {\n            if (errBox) {\n              errBox.textContent = data.message || data.error || 'Password reset failed.';\n              errBox.style.display = 'block';\n            }\n          }\n        } catch (err) {\n          if (errBox) {\n            errBox.textContent = 'Network error while resetting password.';\n            errBox.style.display = 'block';\n          }\n        } finally {\n          if (btn) btn.disabled = false;\n        }\n      };\n    }\n  }\n\n  saveOpenDiagramIds() {\n    try {\n      localStorage.setItem('bowtie_open_diagram_ids', JSON.stringify(this.openDiagramIds || []));\n    } catch (_) {}\n  }\n\n  openDiagram(diagId) {\n    if (!this.openDiagramIds.includes(diagId)) {\n      this.openDiagramIds.push(diagId);\n      this.saveOpenDiagramIds();\n    }\n    this.switchDiagram(diagId);\n    this.switchToView('canvas');\n    this.renderDiagramTabs();\n  }\n\n  closeDiagram(diagId, e) {\n    if (e) e.stopPropagation();\n    this.openDiagramIds = this.openDiagramIds.filter(id => id !== diagId);\n    this.saveOpenDiagramIds();\n\n    if (diagId === 'admin') {\n      if (this.currentView === 'admin') {\n        if (this.openDiagramIds.length > 0) {\n          const nextId = this.openDiagramIds[this.openDiagramIds.length - 1];\n          if (nextId === 'admin') {\n            this.switchToView('admin');\n          } else {\n            this.switchDiagram(nextId);\n            this.switchToView('canvas');\n          }\n        } else {\n          this.switchToView('drive');\n        }\n      }\n    } else if (this.currentDiagram && this.currentDiagram.id === diagId) {\n      if (this.openDiagramIds.length > 0) {\n        const nextId = this.openDiagramIds[this.openDiagramIds.length - 1];\n        if (nextId === 'admin') {\n          this.switchToView('admin');\n        } else {\n          this.switchDiagram(nextId);\n          this.switchToView('canvas');\n        }\n      } else {\n        this.currentDiagram = null;\n        this.switchToView('drive');\n      }\n    } else if (this.openDiagramIds.length === 0 && this.currentView !== 'drive') {\n      this.currentDiagram = null;\n      this.switchToView('drive');\n    }\n    this.renderDiagramTabs();\n    this.initDiagramSelector();\n    this.updateUndoRedoUI();\n    if (this.tree) this.tree.render();\n  }\n\n  clearTabDropIndicators() {\n    document.querySelectorAll('.diagram-tab').forEach(el => {\n      el.classList.remove('drop-target-left', 'drop-target-right');\n    });\n  }\n\n  setupTabDragEvents(tab, tabId, container) {\n    tab.setAttribute('draggable', 'true');\n\n    // Desktop Mouse Drag & Drop\n    tab.addEventListener('dragstart', (e) => {\n      e.dataTransfer.setData('text/plain', tabId);\n      e.dataTransfer.effectAllowed = 'move';\n      this._draggedTabId = tabId;\n      setTimeout(() => tab.classList.add('tab-dragging'), 0);\n    });\n\n    tab.addEventListener('dragover', (e) => {\n      e.preventDefault();\n      if (this._draggedTabId && this._draggedTabId !== tabId) {\n        e.dataTransfer.dropEffect = 'move';\n        const rect = tab.getBoundingClientRect();\n        const isAfter = (e.clientX - rect.left) > (rect.width / 2);\n        this.clearTabDropIndicators();\n        if (isAfter) {\n          tab.classList.add('drop-target-right');\n        } else {\n          tab.classList.add('drop-target-left');\n        }\n      }\n    });\n\n    tab.addEventListener('dragleave', (e) => {\n      const rect = tab.getBoundingClientRect();\n      if (e.clientX < rect.left || e.clientX >= rect.right || e.clientY < rect.top || e.clientY >= rect.bottom) {\n        tab.classList.remove('drop-target-left', 'drop-target-right');\n      }\n    });\n\n    tab.addEventListener('drop', (e) => {\n      e.preventDefault();\n      const sourceId = e.dataTransfer.getData('text/plain') || this._draggedTabId;\n      const rect = tab.getBoundingClientRect();\n      const isAfter = (e.clientX - rect.left) > (rect.width / 2);\n      this.clearTabDropIndicators();\n      if (sourceId && sourceId !== tabId) {\n        this.reorderOpenTabs(sourceId, tabId, isAfter);\n      }\n    });\n\n    tab.addEventListener('dragend', () => {\n      tab.classList.remove('tab-dragging');\n      this.clearTabDropIndicators();\n      this._draggedTabId = null;\n    });\n\n    // Touch / Mobile Drag & Drop\n    let touchStartX = 0;\n    let touchStartY = 0;\n    let isTouchDragging = false;\n\n    tab.addEventListener('touchstart', (e) => {\n      if (e.touches.length !== 1) return;\n      const touch = e.touches[0];\n      touchStartX = touch.clientX;\n      touchStartY = touch.clientY;\n      isTouchDragging = false;\n      this._draggedTabId = tabId;\n    }, { passive: true });\n\n    tab.addEventListener('touchmove', (e) => {\n      if (e.touches.length !== 1) return;\n      const touch = e.touches[0];\n      const dx = touch.clientX - touchStartX;\n      const dy = touch.clientY - touchStartY;\n      if (!isTouchDragging && Math.hypot(dx, dy) > 8) {\n        isTouchDragging = true;\n        tab.classList.add('tab-dragging');\n      }\n      if (isTouchDragging) {\n        if (e.cancelable) e.preventDefault();\n        const elemBelow = document.elementFromPoint(touch.clientX, touch.clientY);\n        const targetTab = elemBelow ? elemBelow.closest('.diagram-tab') : null;\n        this.clearTabDropIndicators();\n        if (targetTab && targetTab !== tab) {\n          if (targetTab.classList.contains('doc-tab')) {\n            targetTab.classList.add('drop-target-right');\n          } else {\n            const rect = targetTab.getBoundingClientRect();\n            const isAfter = (touch.clientX - rect.left) > (rect.width / 2);\n            if (isAfter) {\n              targetTab.classList.add('drop-target-right');\n            } else {\n              targetTab.classList.add('drop-target-left');\n            }\n          }\n        }\n      }\n    }, { passive: false });\n\n    tab.addEventListener('touchend', (e) => {\n      if (isTouchDragging) {\n        tab.classList.remove('tab-dragging');\n        const touch = e.changedTouches[0];\n        const elemBelow = document.elementFromPoint(touch.clientX, touch.clientY);\n        const targetTab = elemBelow ? elemBelow.closest('.diagram-tab') : null;\n        this.clearTabDropIndicators();\n        if (targetTab && targetTab !== tab) {\n          if (targetTab.classList.contains('doc-tab')) {\n            this.reorderOpenTabs(tabId, null, false);\n          } else {\n            const targetId = targetTab.dataset.tabId || targetTab.dataset.diagramId;\n            const rect = targetTab.getBoundingClientRect();\n            const isAfter = (touch.clientX - rect.left) > (rect.width / 2);\n            if (targetId && targetId !== tabId) {\n              this.reorderOpenTabs(tabId, targetId, isAfter);\n            }\n          }\n        }\n        isTouchDragging = false;\n        this._draggedTabId = null;\n        if (e.cancelable) e.preventDefault();\n      } else {\n        this._draggedTabId = null;\n      }\n    });\n\n    tab.addEventListener('touchcancel', () => {\n      tab.classList.remove('tab-dragging');\n      this.clearTabDropIndicators();\n      isTouchDragging = false;\n      this._draggedTabId = null;\n    });\n  }\n\n  reorderOpenTabs(sourceId, targetId, insertAfter) {\n    if (!sourceId) return;\n    if (sourceId === targetId) return;\n    const oldIndex = this.openDiagramIds.indexOf(sourceId);\n    if (oldIndex === -1) return;\n\n    const newIds = [...this.openDiagramIds];\n    newIds.splice(oldIndex, 1);\n\n    if (!targetId || targetId === 'drive' || targetId === 'doc') {\n      newIds.unshift(sourceId);\n    } else {\n      const targetIndex = newIds.indexOf(targetId);\n      if (targetIndex === -1) {\n        newIds.push(sourceId);\n      } else {\n        const insertIndex = insertAfter ? targetIndex + 1 : targetIndex;\n        newIds.splice(insertIndex, 0, sourceId);\n      }\n    }\n\n    this.openDiagramIds = newIds;\n    this.saveOpenDiagramIds();\n    this.renderDiagramTabs();\n  }\n\n  renderDiagramTabs() {\n    const container = document.getElementById('tabsScrollContainer');\n    if (!container) return;\n\n    container.innerHTML = '';\n\n    // Permanent Tab #0: Documents Drive folder icon (cannot be closed, not draggable)\n    const docTab = document.createElement('div');\n    docTab.className = `diagram-tab doc-tab ${this.currentView === 'drive' ? 'active' : ''}`;\n    docTab.title = 'Documents Drive (My Files)';\n    docTab.draggable = false;\n    docTab.innerHTML = `\n      <svg class=\"tab-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n        <path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/>\n      </svg>\n    `;\n    docTab.onclick = () => {\n      this.switchToView('drive');\n    };\n    // Drop target to allow dragging tabs back to index 0\n    docTab.addEventListener('dragover', (e) => {\n      e.preventDefault();\n      if (this._draggedTabId) {\n        e.dataTransfer.dropEffect = 'move';\n        this.clearTabDropIndicators();\n        docTab.classList.add('drop-target-right');\n      }\n    });\n    docTab.addEventListener('dragleave', () => {\n      docTab.classList.remove('drop-target-right');\n    });\n    docTab.addEventListener('drop', (e) => {\n      e.preventDefault();\n      docTab.classList.remove('drop-target-right');\n      const sourceId = e.dataTransfer.getData('text/plain') || this._draggedTabId;\n      this.clearTabDropIndicators();\n      if (sourceId) {\n        this.reorderOpenTabs(sourceId, null, false);\n      }\n    });\n    container.appendChild(docTab);\n\n    // Filter valid open IDs: diagram IDs must match workspace, 'admin' only if user is admin\n    const diagrams = (this.workspace && this.workspace.diagrams) || [];\n    const validOpenIds = this.openDiagramIds.filter(id => {\n      if (id === 'admin') {\n        return !!(this.currentUser && this.currentUser.is_admin);\n      }\n      return diagrams.some(d => d.id === id);\n    });\n    this.openDiagramIds = validOpenIds;\n\n    this.openDiagramIds.forEach(id => {\n      if (id === 'admin') {\n        const tab = document.createElement('div');\n        const isActive = this.currentView === 'admin';\n        tab.className = `diagram-tab admin-tab-item ${isActive ? 'active' : ''}`;\n        tab.dataset.tabId = 'admin';\n        tab.dataset.diagramId = 'admin';\n        tab.title = 'Bowtie RISK Admin Portal';\n\n        tab.innerHTML = `\n          <svg class=\"tab-icon\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#7c3aed\" stroke-width=\"2\">\n            <path d=\"M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 14h-2v-2h2zm0-4h-2V7h2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>\n          </svg>\n          <span class=\"tab-title tab-title-admin\" style=\"color: #7c3aed; font-weight: 600;\">Admin</span>\n          <button type=\"button\" class=\"tab-close-btn\" title=\"Close Admin tab\" style=\"color: #7c3aed;\">&times;</button>\n        `;\n\n        tab.onclick = () => {\n          this.switchToView('admin');\n        };\n\n        const closeBtn = tab.querySelector('.tab-close-btn');\n        if (closeBtn) {\n          closeBtn.onclick = (e) => {\n            e.stopPropagation();\n            this.closeDiagram('admin', e);\n          };\n        }\n\n        this.setupTabDragEvents(tab, 'admin', container);\n        container.appendChild(tab);\n      } else {\n        const diag = diagrams.find(d => d.id === id);\n        if (!diag) return;\n\n        const tab = document.createElement('div');\n        const isActive = this.currentView === 'canvas' && this.currentDiagram && this.currentDiagram.id === id;\n        tab.className = `diagram-tab ${isActive ? 'active' : ''}`;\n        tab.dataset.tabId = id;\n        tab.dataset.diagramId = id;\n        tab.title = diag.title || 'Assessment';\n\n        tab.innerHTML = `\n          <svg class=\"tab-icon\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n            <polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/>\n          </svg>\n          <span class=\"tab-title\">${this.escapeHtml(diag.title || 'Untitled Assessment')}</span>\n          <button type=\"button\" class=\"tab-close-btn\" title=\"Close assessment tab\">&times;</button>\n        `;\n\n        tab.onclick = () => {\n          this.switchDiagram(id);\n          this.switchToView('canvas');\n        };\n\n        const closeBtn = tab.querySelector('.tab-close-btn');\n        if (closeBtn) {\n          closeBtn.onclick = (e) => {\n            e.stopPropagation();\n            this.closeDiagram(id, e);\n          };\n        }\n\n        this.setupTabDragEvents(tab, id, container);\n        container.appendChild(tab);\n      }\n    });\n\n    // Chrome-style \"+\" new tab button placed immediately to the right of open tabs\n    const btnTabAdd = document.createElement('button');\n    btnTabAdd.id = 'btnTabAddDiagram';\n    btnTabAdd.type = 'button';\n    btnTabAdd.className = 'tab-btn-add';\n    btnTabAdd.title = 'Create new assessment tab';\n    btnTabAdd.innerHTML = `\n      <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n        <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"/>\n        <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/>\n      </svg>\n    `;\n    btnTabAdd.onclick = () => {\n      if (!this.isInstanceLicensedForEdit()) {\n        this.showUnlicensedPrompt();\n        return;\n      }\n      const form = document.getElementById('newDiagramForm');\n      if (form) form.reset();\n      const chk = document.getElementById('chkAddToCurrentDiagram');\n      if (chk) chk.checked = false;\n      const gTitle = document.getElementById('groupNewDiagTitle');\n      const gDesc = document.getElementById('groupNewDiagDesc');\n      const heading = document.getElementById('modalNewDiagramHeading');\n      const btnSub = document.getElementById('btnSubmitNewDiagram');\n      if (gTitle) gTitle.style.display = 'block';\n      if (gDesc) gDesc.style.display = 'block';\n      if (heading) heading.textContent = 'Create New Bowtie Diagram';\n      if (btnSub) btnSub.textContent = 'Create Diagram on Canvas';\n      const modal = document.getElementById('modalNewDiagram');\n      if (modal) modal.classList.add('show');\n    };\n    container.appendChild(btnTabAdd);\n  }\n\n  initDiagramSelector() {\n    const select = document.getElementById('diagramSelect');\n    if (!select || !this.workspace || !this.workspace.diagrams) return;\n\n    select.innerHTML = '';\n    if (this.workspace.diagrams.length === 0) {\n      const opt = document.createElement('option');\n      opt.value = '';\n      opt.textContent = 'No Diagrams';\n      select.appendChild(opt);\n    } else {\n      this.workspace.diagrams.forEach(diag => {\n        const opt = document.createElement('option');\n        opt.value = diag.id;\n        opt.textContent = diag.title;\n        if (this.currentDiagram && diag.id === this.currentDiagram.id) {\n          opt.selected = true;\n        }\n        select.appendChild(opt);\n      });\n    }\n\n    select.onchange = (e) => {\n      if (e.target.value) {\n        this.openDiagram(e.target.value);\n      }\n    };\n\n    const titleEl = document.getElementById('currentDiagramTitle');\n    if (titleEl) {\n      if (this.currentDiagram) {\n        titleEl.textContent = this.currentDiagram.title;\n      } else {\n        titleEl.textContent = 'No Diagram Selected';\n      }\n    }\n  }\n\n  switchDiagram(diagId) {\n    if (!this.workspace || !this.workspace.diagrams) return;\n    const diag = this.workspace.diagrams.find(d => d.id === diagId);\n    if (!diag) return;\n\n    const prevDiagId = this.currentDiagram ? this.currentDiagram.id : null;\n\n    // Immediately dismiss any active text boxes, virtual keyboards, and open modals to avoid stuck browser tooltips\n    if (document.activeElement && typeof document.activeElement.blur === 'function') {\n      document.activeElement.blur();\n    }\n    document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));\n    document.querySelectorAll('.input-error').forEach(el => el.classList.remove('input-error'));\n\n    this.currentDiagram = diag;\n\n    if (!this.openDiagramIds.includes(diagId)) {\n      this.openDiagramIds.push(diagId);\n    }\n\n    const select = document.getElementById('diagramSelect');\n    if (select) select.value = diagId;\n\n    const titleEl = document.getElementById('currentDiagramTitle');\n    if (titleEl) titleEl.textContent = diag.title;\n\n    // Center on this diagram's top event & zoom to fit\n    if (this.canvas) {\n      if (diag.top_event_id) {\n        this.canvas.setActiveNode(diag.top_event_id);\n      } else {\n        const first = this.workspace.nodes ? this.workspace.nodes.find(n => n.diagram_id === diag.id) : null;\n        if (first) {\n          this.canvas.setActiveNode(first.id);\n        }\n      }\n      this.canvas.render();\n      requestAnimationFrame(() => {\n        this.canvas.zoomFitAndCenterTopEvent(diag.top_event_id);\n      });\n    }\n\n    if (this.tree) this.tree.render();\n    this.renderDiagramTabs();\n    if (this.situationRunner) {\n      this.situationRunner.populateThreats();\n    }\n    this.updateUndoRedoUI();\n    this.updateDiagramPermissionUI();\n\n    // Streamline real-time presence connection across diagram switches\n    if (prevDiagId !== diagId) {\n      if (prevDiagId) {\n        this.leaveCurrentDiagramPresence(prevDiagId);\n      }\n      if (this.canvas) {\n        this.canvas.clearRemotePresences();\n      }\n      this.collaborators.clear();\n      this.updateCollaboratorsStack();\n      this.initRealtimeCollaboration();\n    }\n  }\n\n  leaveCurrentDiagramPresence(diagId) {\n    if (!diagId || !this.sessionId) return;\n    const payload = JSON.stringify({ diagram_id: diagId, session_id: this.sessionId });\n    if (navigator.sendBeacon) {\n      navigator.sendBeacon('/api/realtime/leave', payload);\n    } else {\n      this.apiFetch('/api/realtime/leave', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: payload,\n      }).catch(() => {});\n    }\n  }\n\n  getUserByEmail(email) {\n    if (!email) return null;\n    const lower = email.trim().toLowerCase();\n    if (this.currentUser && this.currentUser.email && this.currentUser.email.toLowerCase() === lower) {\n      return this.currentUser;\n    }\n    if (this.directoryUsersMap && this.directoryUsersMap[lower]) {\n      return this.directoryUsersMap[lower];\n    }\n    if (this.workspace && this.workspace.users) {\n      const u = this.workspace.users.find(x => x.email && x.email.toLowerCase() === lower);\n      if (u) return u;\n    }\n    return null;\n  }\n\n  getUserAvatarHtml(userOrEmail, size = 30) {\n    if (!userOrEmail) return `<div class=\"avatar\" style=\"width: ${size}px; height: ${size}px; min-width: ${size}px; min-height: ${size}px; aspect-ratio: 1 / 1; border-radius: 50%;\">?</div>`;\n    let user = typeof userOrEmail === 'string' ? this.getUserByEmail(userOrEmail) : userOrEmail;\n    if (!user && typeof userOrEmail === 'string') {\n      const email = userOrEmail.trim();\n      const inits = email.substring(0, 2).toUpperCase();\n      const hashVal = email.split('').reduce((acc, c) => acc + c.charCodeAt(0), 0);\n      const palette = ['#2563eb', '#7c3aed', '#059669', '#d97706', '#dc2626', '#0891b2', '#4f46e5', '#0284c7'];\n      const bg = palette[hashVal % palette.length];\n      user = { email, name: email, avatar_initials: inits, avatar_color: bg };\n    }\n    if (user && user.email && (!user.avatar_url || !user.avatar_color)) {\n      const found = this.getUserByEmail(user.email);\n      if (found) {\n        user = { ...found, ...user, avatar_url: user.avatar_url || found.avatar_url, avatar_color: user.avatar_color || found.avatar_color };\n      }\n    }\n\n    const avatarUrl = user ? user.avatar_url : null;\n    if (avatarUrl && typeof avatarUrl === 'string' && avatarUrl.trim()) {\n      const trimmed = avatarUrl.trim();\n      if (trimmed.startsWith('data:image/') || trimmed.startsWith('http://') || trimmed.startsWith('https://') || trimmed.startsWith('/')) {\n        return `<div class=\"avatar avatar-img-wrap\" style=\"width: ${size}px; height: ${size}px; min-width: ${size}px; min-height: ${size}px; max-width: ${size}px; max-height: ${size}px; aspect-ratio: 1 / 1; border-radius: 50%; overflow: hidden; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;\"><img src=\"${this.escapeHtml(trimmed)}\" class=\"avatar-img\" width=\"${size}\" height=\"${size}\" style=\"width: ${size}px; height: ${size}px; min-width: ${size}px; min-height: ${size}px; max-width: ${size}px; max-height: ${size}px; aspect-ratio: 1 / 1; border-radius: 50%; object-fit: cover; display: block; flex-shrink: 0;\" alt=\"Avatar\" /></div>`;\n      } else {\n        const fontSize = Math.round(size * 0.55);\n        return `<div class=\"avatar\" style=\"width: ${size}px; height: ${size}px; min-width: ${size}px; min-height: ${size}px; font-size: ${fontSize}px; background: #e2e8f0; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; aspect-ratio: 1 / 1; flex-shrink: 0; line-height: 1;\">${this.escapeHtml(trimmed)}</div>`;\n      }\n    }\n    const initials = user ? (user.avatar_initials || (user.name || user.email || 'U').substring(0, 2).toUpperCase()) : 'U';\n    const bg = user && user.is_admin ? '#7c3aed' : (user && user.avatar_color ? user.avatar_color : '#2563eb');\n    const fontSize = Math.max(9, Math.round(size * 0.38));\n    return `<div class=\"avatar\" style=\"width: ${size}px; height: ${size}px; min-width: ${size}px; min-height: ${size}px; font-size: ${fontSize}px; background: ${bg}; color: #ffffff; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; aspect-ratio: 1 / 1; flex-shrink: 0; font-weight: 600;\">${this.escapeHtml(initials)}</div>`;\n  }\n\n  updateUserUI() {\n    if (!this.currentUser || !this.currentUser.email || this.currentUser.id === 'guest') {\n      this.showAuthWall();\n      return;\n    }\n    const emailEl = document.getElementById('userEmailDisplay');\n    const avatarEl = document.getElementById('userAvatar');\n\n    // Dropdown Profile Elements\n    const dropAvatar = document.getElementById('dropdownAvatar');\n    const dropName = document.getElementById('dropdownUserName');\n    const dropEmail = document.getElementById('dropdownUserEmail');\n    const dropOrg = document.getElementById('dropdownUserOrg');\n    const dropRole = document.getElementById('dropdownUserRole');\n    const btnSignOut = document.getElementById('btnSignOut');\n\n    if (emailEl) emailEl.textContent = this.currentUser.email;\n\n    if (avatarEl) {\n      avatarEl.innerHTML = this.getUserAvatarHtml(this.currentUser, 28);\n      avatarEl.style.background = 'transparent';\n      avatarEl.style.padding = '0';\n      avatarEl.style.width = '28px';\n      avatarEl.style.height = '28px';\n      avatarEl.style.minWidth = '28px';\n      avatarEl.style.minHeight = '28px';\n      avatarEl.style.borderRadius = '50%';\n      avatarEl.style.overflow = 'hidden';\n      avatarEl.style.aspectRatio = '1 / 1';\n      avatarEl.style.flexShrink = '0';\n    }\n    if (dropAvatar) {\n      dropAvatar.innerHTML = this.getUserAvatarHtml(this.currentUser, 56);\n      dropAvatar.style.background = 'transparent';\n      dropAvatar.style.padding = '0';\n      dropAvatar.style.width = '56px';\n      dropAvatar.style.height = '56px';\n      dropAvatar.style.minWidth = '56px';\n      dropAvatar.style.minHeight = '56px';\n      dropAvatar.style.borderRadius = '50%';\n      dropAvatar.style.overflow = 'hidden';\n      dropAvatar.style.aspectRatio = '1 / 1';\n      dropAvatar.style.flexShrink = '0';\n    }\n    if (dropName) dropName.textContent = this.currentUser.name || this.currentUser.email.split('@')[0];\n    if (dropEmail) dropEmail.textContent = this.currentUser.email;\n    if (dropOrg) {\n      dropOrg.textContent = this.currentUser.organization || 'Bowtie Risk Team';\n      dropOrg.style.display = this.currentUser.organization ? 'inline-block' : 'none';\n    }\n    if (dropRole) {\n      dropRole.textContent = this.currentUser.role || 'Risk Assessor';\n      dropRole.style.display = this.currentUser.role ? 'inline-block' : 'none';\n    }\n    if (btnSignOut) btnSignOut.style.display = 'flex';\n\n    // Admin Portal Trigger & Badges\n    const btnOpenAdminPortal = document.getElementById('btnOpenAdminPortal');\n    const btnHeaderAdminPortal = document.getElementById('btnHeaderAdminPortal');\n    const dropAdminBadge = document.getElementById('dropdownUserAdminBadge');\n    const isAdmin = Boolean(this.currentUser && this.currentUser.is_admin);\n\n    if (btnOpenAdminPortal) btnOpenAdminPortal.style.display = isAdmin ? 'flex' : 'none';\n    if (btnHeaderAdminPortal) btnHeaderAdminPortal.style.display = isAdmin ? 'inline-flex' : 'none';\n    if (dropAdminBadge) dropAdminBadge.style.display = isAdmin ? 'inline-block' : 'none';\n  }\n\n  // Switch between Google Drive Documents Landing View, Bowtie Canvas, and Admin Portal Tab\n  switchToView(viewName) {\n    this.currentView = viewName;\n    const driveView = document.getElementById('documentsLandingView');\n    const canvasView = document.getElementById('canvasWorkspaceView');\n    const adminView = document.getElementById('adminPortalView');\n    const diagramNavControls = document.getElementById('diagramNavControls');\n    const btnSidebarToggleLeft = document.getElementById('btnSidebarToggleLeft');\n    const btnSidebarToggleRight = document.getElementById('btnSidebarToggleRight');\n    const mobileMenu = document.getElementById('mobileMenuPanel');\n    if (mobileMenu) mobileMenu.style.display = 'none';\n\n    const collabStack = document.getElementById('collaboratorsStack');\n\n    if (viewName === 'drive') {\n      if (collabStack) collabStack.style.display = 'none';\n      this.disconnectRealtimeCollaboration();\n      if (driveView) driveView.style.display = 'flex';\n      if (canvasView) canvasView.style.display = 'none';\n      if (adminView) adminView.style.display = 'none';\n      if (diagramNavControls) diagramNavControls.style.display = 'none';\n      if (btnSidebarToggleLeft) btnSidebarToggleLeft.style.display = 'none';\n      if (btnSidebarToggleRight) btnSidebarToggleRight.style.display = 'none';\n      if (this.documents) this.documents.render();\n    } else if (viewName === 'admin') {\n      if (collabStack) collabStack.style.display = 'none';\n      this.disconnectRealtimeCollaboration();\n      if (driveView) driveView.style.display = 'none';\n      if (canvasView) canvasView.style.display = 'none';\n      if (adminView) adminView.style.display = 'flex';\n      if (diagramNavControls) diagramNavControls.style.display = 'none';\n      if (btnSidebarToggleLeft) btnSidebarToggleLeft.style.display = 'none';\n      if (btnSidebarToggleRight) btnSidebarToggleRight.style.display = 'none';\n      this.switchAdminTab('users');\n      this.loadAdminUsers();\n      this.loadAdminConfig();\n    } else {\n      if (collabStack) collabStack.style.display = 'flex';\n      if (driveView) driveView.style.display = 'none';\n      if (canvasView) canvasView.style.display = 'flex';\n      if (adminView) adminView.style.display = 'none';\n      if (diagramNavControls) diagramNavControls.style.display = 'inline-flex';\n      if (btnSidebarToggleLeft) btnSidebarToggleLeft.style.display = 'inline-flex';\n      if (btnSidebarToggleRight) btnSidebarToggleRight.style.display = 'inline-flex';\n      if (this.canvas) {\n        this.canvas.render();\n        this.canvas.updateTransform();\n        if (this.currentDiagram) {\n          requestAnimationFrame(() => {\n            this.canvas.zoomFitAndCenterTopEvent(this.currentDiagram.top_event_id);\n          });\n        }\n      }\n      if (this.currentDiagram) {\n        this.initRealtimeCollaboration();\n      }\n      if (this.tree) this.tree.render();\n      this.updateSidebarStateUI();\n    }\n    this.renderDiagramTabs();\n  }\n\n  // Synchronize sidebar pane collapse state with header icons and edge tabs\n  updateSidebarStateUI() {\n    const leftPane = document.getElementById('leftPane');\n    const rightPane = document.getElementById('rightPane');\n    const btnCollapseLeft = document.getElementById('btnCollapseLeft');\n    const btnCollapseRight = document.getElementById('btnCollapseRight');\n    const edgeLeft = document.getElementById('btnEdgeToggleLeft');\n    const edgeRight = document.getElementById('btnEdgeToggleRight');\n    const isMobile = window.innerWidth <= 900;\n\n    if (leftPane) {\n      const isLeftClosed = isMobile ? !leftPane.classList.contains('drawer-open') : leftPane.classList.contains('collapsed');\n      if (btnCollapseLeft) btnCollapseLeft.textContent = isLeftClosed ? '›' : '‹';\n      if (edgeLeft) edgeLeft.style.display = (this.currentView === 'canvas' && isLeftClosed) ? 'flex' : 'none';\n    }\n\n    if (rightPane) {\n      const isRightClosed = isMobile ? !rightPane.classList.contains('drawer-open') : rightPane.classList.contains('collapsed');\n      if (btnCollapseRight) btnCollapseRight.textContent = isRightClosed ? '‹' : '›';\n      if (edgeRight) edgeRight.style.display = (this.currentView === 'canvas' && isRightClosed) ? 'flex' : 'none';\n    }\n  }\n\n  // Custom Nomenclature & Colors from User Settings\n  getCustomNames() {\n    if (this.currentUser && this.currentUser.settings && this.currentUser.settings.custom_names) {\n      return this.currentUser.settings.custom_names;\n    }\n    return {};\n  }\n\n  getCustomColors() {\n    if (this.currentUser && this.currentUser.settings && this.currentUser.settings.custom_colors) {\n      return this.currentUser.settings.custom_colors;\n    }\n    return {};\n  }\n\n  getNodeDisplayTitle(node) {\n    if (!node) return '';\n    const typeKey = node.node_type;\n    const defaultLabels = {\n      hazard: 'Hazard',\n      top_event: 'Top event',\n      threat: 'Threat',\n      threat_barrier: 'Threat Barrier',\n      outcome_barrier: 'Outcome Barrier',\n      outcome: 'Outcome',\n      escalation_factor: 'Escalation Factor',\n      mitigation: 'Mitigation',\n    };\n    const defaultLabel = defaultLabels[typeKey] || typeKey || '';\n    const customNames = this.getCustomNames();\n    const customName = customNames[typeKey];\n\n    const title = (node.title != null ? String(node.title) : '').trim();\n\n    // If node title has been customized or differs from the default type label\n    if (node.title_customized && title) {\n      return title;\n    }\n    if (title && title.toLowerCase() !== defaultLabel.toLowerCase()) {\n      return title;\n    }\n\n    // Otherwise fallback to custom nomenclature from settings if defined, or title / defaultLabel\n    if (customName && customName.trim()) {\n      return customName.trim();\n    }\n    return title || defaultLabel;\n  }\n\n  // Node Lookups\n  getNodeById(id) {\n    if (!this.workspace || !this.workspace.nodes || !id) return null;\n    if (typeof id === 'object' && id.id) return id;\n    return this.workspace.nodes.find(n => n.id === id);\n  }\n\n  // Resolves the owning Outcome for any barrier or node on the right wing\n  getOwningOutcome(barrier, allNodes = null) {\n    if (!barrier) return null;\n    if (barrier.node_type === 'outcome') return barrier;\n    const diagNodes = (allNodes || (this.workspace ? this.workspace.nodes : [])).filter(n => n.diagram_id === barrier.diagram_id);\n    let curr = barrier;\n    const seen = new Set();\n    while (curr && !seen.has(curr.id)) {\n      seen.add(curr.id);\n      if (curr.parent_id) {\n        const parent = diagNodes.find(n => n.id === curr.parent_id);\n        if (parent) {\n          if (parent.node_type === 'outcome') return parent;\n          if (parent.node_type === 'outcome_barrier') {\n            curr = parent;\n            continue;\n          }\n        }\n      }\n      break;\n    }\n    const childOc = diagNodes.find(n => n.node_type === 'outcome' && n.parent_id === barrier.id);\n    if (childOc) return childOc;\n\n    const outcomes = diagNodes.filter(n => n.node_type === 'outcome');\n    if (outcomes.length === 0) return null;\n    const closest = outcomes.reduce((best, oc) => {\n      const dBest = Math.abs((barrier.y || 0) - (best.y || 0));\n      const dCurr = Math.abs((barrier.y || 0) - (oc.y || 0));\n      return dCurr < dBest ? oc : best;\n    }, outcomes[0]);\n    return closest || null;\n  }\n\n  // Ensure standard card height of 80px and canonical widths\n  normalizeNodeDimensions() {\n    if (!this.workspace || !this.workspace.nodes) return;\n    this.workspace.nodes.forEach(n => {\n      if (n.node_type === 'top_event') {\n        n.height = 130;\n        n.width = n.width || 130;\n      } else if (n.node_type === 'hazard') {\n        n.height = 90;\n        n.width = n.width || 150;\n      } else {\n        n.height = 80;\n        if (n.node_type === 'threat' || n.node_type === 'outcome') {\n          n.width = n.width || 140;\n        } else if (n.node_type === 'mitigation') {\n          n.width = n.width || 120;\n        } else {\n          n.width = n.width || 135;\n        }\n      }\n    });\n  }\n\n  // Ensure barriers added to outcomes precede the terminal outcome in the hierarchy and sibling barriers flow left-to-right\n  fixInvertedOutcomeBarriers() {\n    if (!this.workspace || !this.workspace.nodes || !this.workspace.diagrams) return;\n    const nodes = this.workspace.nodes;\n    let fixed = false;\n\n    // Auto-clean old boilerplate prewritten descriptions so they show suggested grey text instead\n    const boilerplateDescs = new Set([\n      'New Preventative Barrier',\n      'Safety Critical Barrier',\n      'New Escalation Factor',\n      'Mitigation / Control Barrier',\n      'New Identified Threat / Cause',\n      'New Identified Consequence / Outcome',\n      'Identified Hazard Source',\n      'Loss of Primary Containment',\n    ]);\n    nodes.forEach(n => {\n      if (n.description && boilerplateDescs.has(n.description.trim())) {\n        n.description = '';\n        fixed = true;\n      }\n    });\n\n    // 0. Auto-repair barriers wrongly attached to escalation factors or mitigations\n    let repairPasses = 0;\n    while (repairPasses < 10) {\n      let passFixed = false;\n      nodes.forEach(n => {\n        if ((n.node_type === 'outcome_barrier' || n.node_type === 'threat_barrier') && n.parent_id) {\n          let parent = nodes.find(p => p.id === n.parent_id);\n          if (parent && (parent.node_type === 'escalation_factor' || parent.node_type === 'mitigation')) {\n            while (parent && (parent.node_type === 'escalation_factor' || parent.node_type === 'mitigation')) {\n              parent = nodes.find(p => p.id === parent.parent_id);\n            }\n            if (parent) {\n              n.parent_id = parent.id;\n              if (parent.node_type === 'threat' || parent.node_type === 'threat_barrier') {\n                n.node_type = 'threat_barrier';\n                if (!n.title || n.title === 'Outcome Barrier') n.title = 'Threat Barrier';\n              } else {\n                n.node_type = 'outcome_barrier';\n                if (!n.title || n.title === 'Threat Barrier') n.title = 'Outcome Barrier';\n              }\n              n.y = parent.y;\n              fixed = true;\n              passFixed = true;\n            }\n          }\n        }\n      });\n      if (!passFixed) break;\n      repairPasses++;\n    }\n\n    // 1. Auto-repair orphaned outcomes (e.g. parent barrier was deleted/undone)\n    nodes.forEach(n => {\n      if (n.node_type === 'outcome') {\n        const parent = n.parent_id ? nodes.find(p => p.id === n.parent_id) : null;\n        if (!parent) {\n          const topEvents = nodes.filter(te => te.diagram_id === n.diagram_id && te.node_type === 'top_event');\n          if (topEvents.length > 0) {\n            const closest = topEvents.slice().sort((a, b) => Math.hypot((a.x || 0) - (n.x || 0), (a.y || 0) - (n.y || 0)) - Math.hypot((b.x || 0) - (n.x || 0), (b.y || 0) - (n.y || 0)))[0];\n            n.parent_id = closest.id;\n            fixed = true;\n          }\n        }\n      }\n    });\n\n    // 2. Heal legacy inverted barrier-outcome links (ensure outcomes connect to topEvent and barriers to outcome)\n    nodes.forEach(n => {\n      if (n.node_type === 'outcome' && n.parent_id) {\n        const parent = nodes.find(p => p.id === n.parent_id);\n        if (parent && parent.node_type === 'outcome_barrier') {\n          // Outcome should be root of right wing branch, not child of barrier\n          const topEvents = nodes.filter(te => te.diagram_id === n.diagram_id && te.node_type === 'top_event');\n          const closest = topEvents.slice().sort((a, b) => Math.abs((a.y || 0) - (n.y || 0)) - Math.abs((b.y || 0) - (n.y || 0)))[0];\n          n.parent_id = closest ? closest.id : null;\n          parent.parent_id = n.id;\n          fixed = true;\n        }\n      }\n    });\n\n    // 3. Normalize branches across diagrams\n    this.workspace.diagrams.forEach(diag => {\n      const diagNodes = nodes.filter(n => n.diagram_id === diag.id);\n      const topEvents = diagNodes.filter(n => n.node_type === 'top_event');\n      if (topEvents.length === 0) return;\n\n      // 3a. Re-link Threat branches (threat -> barrier1 -> barrier2)\n      const threats = diagNodes.filter(n => n.node_type === 'threat');\n      threats.forEach(t => {\n        const chain = this.layout ? this.layout.getLinearChainForThreat(t, diagNodes) : [];\n        if (chain.length > 0) {\n          const sorted = [...chain].sort((a, b) => (a.x || 0) - (b.x || 0));\n          sorted[0].parent_id = t.id;\n          for (let i = 1; i < sorted.length; i++) {\n            sorted[i].parent_id = sorted[i - 1].id;\n          }\n          sorted.forEach(b => {\n            b.y = t.y;\n            const efs = diagNodes\n              .filter(ef => ef.parent_id === b.id && ef.node_type === 'escalation_factor')\n              .sort((a, b) => (a.y || 0) - (b.y || 0));\n            efs.forEach((ef, j) => {\n              ef.x = b.x + ((b.width || 135) - (ef.width || 130)) / 2;\n              ef.y = t.y + 80 + 25 + j * 98;\n            });\n          });\n          fixed = true;\n        }\n      });\n\n      // 3b. Re-link Outcome branches for each top event (outcome > barriers > escalation factor > mitigation)\n      // Running from right to left to attach to the top event\n      const ownership = this.layout\n        ? this.layout.buildClusterOwnership(diagNodes, topEvents)\n        : new Map();\n\n      topEvents.forEach(topEvent => {\n        const branches = this.layout\n          ? this.layout.getClusterRightBranches(topEvent, diagNodes, ownership)\n          : [];\n\n        branches.forEach(branch => {\n          const chain = branch.chain;\n          if (!chain || chain.length === 0) return;\n\n          const barriers = chain.filter(n => n.node_type === 'outcome_barrier');\n          const outcome = chain.find(n => n.node_type === 'outcome');\n\n          if (outcome) {\n            outcome.parent_id = topEvent.id;\n          }\n\n          if (barriers.length > 0) {\n            // Sort barriers along canvas X from left to right: [b_0, ..., b_{k-1}]\n            const sorted = [...barriers].sort((a, b) => (a.x || 0) - (b.x || 0));\n            const k = sorted.length;\n\n            if (outcome) {\n              // Outcome hierarchy is outcome > barriers > escalation factor > mitigation\n              // Running from right to left to attach to the top event:\n              // outcome -> sorted[k-1] -> ... -> sorted[0] -> topEvent\n              sorted[k - 1].parent_id = outcome.id;\n              for (let i = k - 2; i >= 0; i--) {\n                sorted[i].parent_id = sorted[i + 1].id;\n              }\n            } else {\n              // Unattached barriers without an outcome yet: chain right-to-left to topEvent\n              sorted[0].parent_id = topEvent.id;\n              for (let i = 1; i < k; i++) {\n                sorted[i].parent_id = sorted[i - 1].id;\n              }\n            }\n\n            const branchY = (outcome && outcome.y != null) ? outcome.y : sorted[0].y;\n            sorted.forEach(b => {\n              b.y = branchY;\n              const efs = diagNodes\n                .filter(ef => ef.parent_id === b.id && ef.node_type === 'escalation_factor')\n                .sort((a, b) => (a.y || 0) - (b.y || 0));\n              efs.forEach((ef, j) => {\n                ef.x = b.x + ((b.width || 135) - (ef.width || 130)) / 2;\n                ef.y = branchY + 80 + 25 + j * 98;\n              });\n            });\n            fixed = true;\n          } else if (outcome) {\n            outcome.parent_id = topEvent.id;\n          }\n        });\n      });\n    });\n\n    if (fixed) {\n      console.log('Normalized inverted outcome barrier hierarchy');\n    }\n  }\n\n  getActiveDiagram() {\n    if (!this.workspace || !this.workspace.diagrams || this.workspace.diagrams.length === 0) {\n      return null;\n    }\n\n    // 1. If an active node is currently selected on the canvas, prioritize its diagram\n    if (this.canvas && this.canvas.activeNodeId) {\n      const activeNode = this.getNodeById(this.canvas.activeNodeId);\n      if (activeNode && activeNode.diagram_id) {\n        const diag = this.workspace.diagrams.find(d => d.id === activeNode.diagram_id);\n        if (diag) {\n          this.currentDiagram = diag;\n          return diag;\n        }\n      }\n    }\n\n    // 2. Check the diagram selector dropdown in the navbar\n    const select = document.getElementById('diagramSelect');\n    if (select && select.value) {\n      const diag = this.workspace.diagrams.find(d => d.id === select.value);\n      if (diag) {\n        this.currentDiagram = diag;\n        return diag;\n      }\n    }\n\n    // 3. Current active diagram instance\n    if (this.currentDiagram) {\n      return this.currentDiagram;\n    }\n\n    // 4. Default to first diagram\n    this.currentDiagram = this.workspace.diagrams[0];\n    return this.currentDiagram;\n  }\n\n  getCurrentDiagram() {\n    return this.getActiveDiagram();\n  }\n\n  onActiveNodeSelected(nodeId) {\n    this.inspector.inspect(nodeId);\n    this.tree.highlightActive(nodeId);\n\n    // Sync active diagram to the selected node's diagram\n    const node = this.getNodeById(nodeId);\n    if (node && node.diagram_id && this.workspace && this.workspace.diagrams) {\n      const diag = this.workspace.diagrams.find(d => d.id === node.diagram_id);\n      if (diag && (!this.currentDiagram || this.currentDiagram.id !== diag.id)) {\n        this.currentDiagram = diag;\n        const select = document.getElementById('diagramSelect');\n        if (select) select.value = diag.id;\n        const titleEl = document.getElementById('currentDiagramTitle');\n        if (titleEl) titleEl.textContent = diag.title;\n      }\n    }\n  }\n\n  onNodeTextareaChanged(node) {\n    if (this.inspector && this.inspector.activeNode && this.inspector.activeNode.id === node.id) {\n      const inspectDesc = document.getElementById('inspectDesc');\n      if (inspectDesc && inspectDesc.value !== (node.description || '')) {\n        inspectDesc.value = node.description || '';\n      }\n    }\n    this.tree.renderTree();\n    this.scheduleAutoSave(`Edited text of ${node.title}`);\n  }\n\n  setSaveStatusText(text, dotClass = 'saved') {\n    const statusEl = document.getElementById('saveStatus');\n    if (statusEl) {\n      const dot = statusEl.querySelector('.status-dot');\n      if (dot) dot.className = `status-dot ${dotClass}`.trim();\n      const txt = statusEl.querySelector('.status-text');\n      if (txt) txt.textContent = text;\n    }\n    const mobileStatusText = document.getElementById('mobileMenuStatusText');\n    if (mobileStatusText) mobileStatusText.textContent = text;\n    const mobileStatusDot = document.querySelector('.mobile-menu-status .status-dot');\n    if (mobileStatusDot) mobileStatusDot.className = `status-dot ${dotClass}`.trim();\n  }\n\n  broadcastDiagramUpdate(summary = 'Auto-save changes') {\n    if (!this.currentDiagram) return;\n    const diagId = this.currentDiagram.id;\n    const diagNodes = this.workspace && this.workspace.nodes\n      ? this.workspace.nodes.filter(n => n.diagram_id === diagId)\n      : [];\n    const myEmail = this.currentUser ? this.currentUser.email : '';\n\n    this.apiFetch('/api/realtime/broadcast', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        diagram_id: diagId,\n        message: {\n          type: 'diagram_updated',\n          data: {\n            diagram_id: diagId,\n            updated_by: myEmail,\n            session_id: this.sessionId,\n            nodes: diagNodes,\n            summary: summary,\n          }\n        }\n      })\n    }).catch(() => {});\n  }\n\n  // ==========================================================================\n  // AUTO-SAVE ENGINE WITH REAL-TIME DEBOUNCING\n  // ==========================================================================\n  scheduleAutoSave(summary = 'Auto-save changes') {\n    if (!this.canEditCurrentDiagram()) return;\n    this.setSaveStatusText('Saving...', 'saving');\n\n    // Instantly broadcast alteration so other collaborators on this diagram see updates immediately\n    this.broadcastDiagramUpdate(summary);\n\n    if (this.autoSaveTimer) {\n      clearTimeout(this.autoSaveTimer);\n    }\n\n    this.autoSaveTimer = setTimeout(async () => {\n      await this.saveWorkspaceToBackend(summary);\n    }, 450);\n  }\n\n  async saveWorkspaceToBackend(summary) {\n    if (!this.authToken || !this.currentUser || this.currentUser.id === 'guest') return;\n    if (this.isSaving) {\n      this.pendingSaveSummary = summary;\n      return;\n    }\n    this.isSaving = true;\n\n    try {\n      const email = this.currentUser ? this.currentUser.email : 'djb@bowtierisk.com';\n      const res = await this.apiFetch('/api/workspace/autosave', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          workspace: this.workspace,\n          user_email: email,\n          change_summary: summary,\n          session_id: this.sessionId,\n        }),\n      });\n\n      if (res.ok) {\n        this.setSaveStatusText('All changes saved', 'saved');\n        this.updateAuditBadge();\n      } else if (res.status === 403) {\n        const errData = await res.json().catch(() => ({}));\n        if (errData && errData.code === 'LICENSE_REQUIRED') {\n          this.setSaveStatusText('Unlicensed (Read-Only)', '');\n          this.showUnlicensedPrompt();\n        }\n      }\n    } catch (err) {\n      console.warn('Auto-save network error:', err);\n      this.setSaveStatusText('Saved offline', '');\n    } finally {\n      this.isSaving = false;\n      if (this.pendingSaveSummary) {\n        const nextSummary = this.pendingSaveSummary;\n        this.pendingSaveSummary = null;\n        this.saveWorkspaceToBackend(nextSummary);\n      }\n    }\n  }\n\n  async updateAuditBadge() {\n    if (!this.currentUser || !this.currentUser.is_admin) return;\n    try {\n      const res = await this.apiFetch('/api/audit');\n      if (res.ok) {\n        const data = await res.json();\n        const badge = document.getElementById('auditCountBadge');\n        if (badge) badge.textContent = data.total;\n      }\n    } catch (e) {\n      // ignore\n    }\n  }\n\n  // ==========================================================================\n  // ELEMENT CREATION & HIERARCHY LOGIC WITH COLLISION PADDING & PUSH-ASIDE\n  // ==========================================================================\n  handleAddChildNode(parentNode) {\n    if (!this.canEditCurrentDiagram()) {\n      this.showNotification('View Only: You cannot add child elements to this diagram', 'warning');\n      return;\n    }\n    const diagId = parentNode.diagram_id;\n    let expectedChildTitle = 'Element';\n    if (parentNode.node_type === 'threat') expectedChildTitle = 'Threat Barrier';\n    else if (parentNode.node_type === 'outcome') expectedChildTitle = 'Outcome Barrier';\n    else if (parentNode.node_type === 'threat_barrier' || parentNode.node_type === 'outcome_barrier') expectedChildTitle = 'Escalation Factor';\n    else if (parentNode.node_type === 'escalation_factor') expectedChildTitle = 'Mitigation';\n    else if (parentNode.node_type === 'mitigation') expectedChildTitle = 'Mitigation';\n    this.pushUndoState(`Add ${expectedChildTitle} under ${parentNode.title}`, diagId);\n\n    const now = new Date().toISOString();\n    const newId = `node-${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 4)}`;\n\n    let childType = 'threat_barrier';\n    let childTitle = 'Threat Barrier';\n    let childDesc = '';\n    let newX = parentNode.x + 180;\n    let newY = parentNode.y;\n    let newParentId = parentNode.id;\n\n    if (parentNode.node_type === 'threat') {\n      childType = 'threat_barrier';\n      childTitle = 'Threat Barrier';\n      childDesc = '';\n      const pos = this.layout.calculatePlacementForThreatBarrier(parentNode, this.workspace.nodes);\n      newX = pos.x;\n      newY = pos.y;\n    } else if (parentNode.node_type === 'outcome') {\n      childType = 'outcome_barrier';\n      childTitle = 'Outcome Barrier';\n      childDesc = '';\n      // Re-parent outcome so the barrier is placed BEFORE the terminal outcome\n      const prevParentId = parentNode.parent_id;\n      newParentId = prevParentId;\n      parentNode.parent_id = newId;\n      newX = parentNode.x;\n      newY = parentNode.y;\n      parentNode.x += 180;\n      const pEl = document.getElementById(`node-${parentNode.id}`);\n      if (pEl) pEl.style.left = `${parentNode.x}px`;\n    } else if (parentNode.node_type === 'threat_barrier' || parentNode.node_type === 'outcome_barrier') {\n      childType = 'escalation_factor';\n      childTitle = 'Escalation Factor';\n      childDesc = '';\n      const pos = this.layout.calculatePlacementForEscalationFactor(parentNode, this.workspace.nodes);\n      newX = pos.x;\n      newY = pos.y;\n    } else if (parentNode.node_type === 'escalation_factor') {\n      childType = 'mitigation';\n      childTitle = 'Mitigation';\n      childDesc = '';\n      const pos = this.layout.calculatePlacementForMitigation(parentNode, this.workspace.nodes);\n      newX = pos.x;\n      newY = pos.y;\n    } else if (parentNode.node_type === 'mitigation') {\n      const parentEf = this.getNodeById(parentNode.parent_id);\n      if (parentEf) {\n        newParentId = parentEf.id;\n        childType = 'mitigation';\n        childTitle = 'Mitigation';\n        childDesc = '';\n        const pos = this.layout.calculatePlacementForMitigation(parentEf, this.workspace.nodes);\n        newX = pos.x;\n        newY = pos.y;\n      }\n    }\n\n    const newNode = {\n      id: newId,\n      diagram_id: diagId,\n      global_element_id: null,\n      parent_id: newParentId,\n      node_type: childType,\n      title: childTitle,\n      description: '',\n      x: newX,\n      y: newY,\n      width: childType === 'mitigation' ? 120 : (childType === 'escalation_factor' ? 130 : 135),\n      height: 80,\n      responsible_role: childType.includes('barrier') ? 'Operations' : null,\n      effectiveness: childType.includes('barrier') ? 'High' : 'Medium',\n      criticality: childType.includes('barrier') ? 'SCE' : 'Standard',\n      notes: '',\n      use_global_metadata: false,\n      created_at: now,\n      updated_at: now,\n    };\n\n    this.workspace.nodes.push(newNode);\n    this.canvas.render();\n    this.tree.render();\n    this.canvas.setActiveNode(newNode.id);\n    this.layout.animateToBowtieLayout(diagId, { newlyAddedNode: newNode });\n    this.scheduleAutoSave(`Added ${childTitle} under ${parentNode.title}`);\n    if (this.autoAlignEnabled) {\n      this.triggerLineUpElements(true);\n    }\n  }\n\n  // Resolve the active or target Top Event based on current selection or fallback\n  resolveActiveTopEvent(diagId = null) {\n    const targetDiagId = diagId || (this.currentDiagram ? this.currentDiagram.id : null);\n    if (!targetDiagId) return null;\n    const diagNodes = (this.workspace.nodes || []).filter(n => n.diagram_id === targetDiagId);\n    let active = this.getNodeById(this.canvas ? this.canvas.activeNodeId : null);\n\n    if (active && active.diagram_id === targetDiagId) {\n      if (active.node_type === 'top_event') {\n        return active;\n      }\n      if (active.node_type === 'hazard') {\n        const childTe = diagNodes.find(n => n.parent_id === active.id && n.node_type === 'top_event');\n        if (childTe) return childTe;\n        const nearestTe = diagNodes.filter(n => n.node_type === 'top_event')\n          .sort((a, b) => Math.hypot((a.x || 0) - (active.x || 0), (a.y || 0) - (active.y || 0)) - Math.hypot((b.x || 0) - (active.x || 0), (b.y || 0) - (active.y || 0)))[0];\n        if (nearestTe) return nearestTe;\n      }\n      // If active is a child/descendant (threat, barrier, outcome, EF, mitigation)\n      let curr = active;\n      const visited = new Set();\n      while (curr && !visited.has(curr.id)) {\n        visited.add(curr.id);\n        if (curr.node_type === 'top_event') {\n          return curr;\n        }\n        if (curr.node_type === 'hazard') {\n          const childTe = diagNodes.find(n => n.parent_id === curr.id && n.node_type === 'top_event');\n          if (childTe) return childTe;\n        }\n        curr = curr.parent_id ? this.getNodeById(curr.parent_id) : null;\n      }\n      // If not directly traceable upwards, find closest top_event to active node\n      const closestTe = diagNodes.filter(n => n.node_type === 'top_event')\n        .sort((a, b) => Math.hypot((a.x || 0) - (active.x || 0), (a.y || 0) - (active.y || 0)) - Math.hypot((b.x || 0) - (active.x || 0), (b.y || 0) - (active.y || 0)))[0];\n      if (closestTe) return closestTe;\n    }\n\n    // Fallback: diag.top_event_id or first top_event in diagram\n    const diag = this.getActiveDiagram();\n    const defaultTe = (diag && diag.top_event_id)\n      ? diagNodes.find(n => n.id === diag.top_event_id)\n      : null;\n    return defaultTe || diagNodes.find(n => n.node_type === 'top_event') || null;\n  }\n\n  // Add top-level Threat to active diagram (placed collision-free below existing threats on selected hazard/top event)\n  addThreatToCurrentDiagram() {\n    if (!this.canEditCurrentDiagram()) {\n      this.showNotification('View Only: You cannot add threats to this diagram', 'warning');\n      return;\n    }\n    const diag = this.getActiveDiagram();\n    if (!diag) return;\n    const topEvent = this.resolveActiveTopEvent(diag.id);\n    const pos = this.layout.calculateNewThreatPlacement(diag.id, this.workspace.nodes, topEvent);\n\n    const newId = `node-threat-${Date.now().toString(36)}`;\n    const now = new Date().toISOString();\n\n    const threatNode = {\n      id: newId,\n      diagram_id: diag.id,\n      global_element_id: null,\n      parent_id: topEvent ? topEvent.id : null,\n      node_type: 'threat',\n      title: 'Threat',\n      description: '',\n      x: pos.x,\n      y: pos.y,\n      width: 140,\n      height: 80,\n      responsible_role: null,\n      effectiveness: null,\n      criticality: 'High',\n      notes: '',\n      use_global_metadata: false,\n      created_at: now,\n      updated_at: now,\n    };\n\n    this.pushUndoState('Add Threat', diag.id);\n    this.workspace.nodes.push(threatNode);\n    this.canvas.render();\n    this.tree.render();\n    this.canvas.setActiveNode(threatNode.id);\n    this.layout.animateToBowtieLayout(diag.id, { newlyAddedNode: threatNode });\n    this.scheduleAutoSave(`Added new Threat to diagram ${diag.title}`);\n    this.showNotification(`Added new Threat to diagram \"${diag.title}\"`, 'success');\n    if (this.autoAlignEnabled) {\n      this.triggerLineUpElements(true);\n    }\n  }\n\n  // Add top-level Outcome to active diagram (placed collision-free below existing outcomes on selected hazard/top event)\n  addOutcomeToCurrentDiagram() {\n    if (!this.canEditCurrentDiagram()) {\n      this.showNotification('View Only: You cannot add outcomes to this diagram', 'warning');\n      return;\n    }\n    const diag = this.getActiveDiagram();\n    if (!diag) return;\n    const topEvent = this.resolveActiveTopEvent(diag.id);\n    const pos = this.layout.calculateNewOutcomePlacement(diag.id, this.workspace.nodes, topEvent);\n\n    const newId = `node-outcome-${Date.now().toString(36)}`;\n    const now = new Date().toISOString();\n\n    const outcomeNode = {\n      id: newId,\n      diagram_id: diag.id,\n      global_element_id: null,\n      parent_id: topEvent ? topEvent.id : null,\n      node_type: 'outcome',\n      title: 'Outcome',\n      description: '',\n      x: pos.x,\n      y: pos.y,\n      width: 140,\n      height: 80,\n      responsible_role: null,\n      effectiveness: null,\n      criticality: 'Severe',\n      notes: '',\n      use_global_metadata: false,\n      created_at: now,\n      updated_at: now,\n    };\n\n    this.pushUndoState('Add Outcome', diag.id);\n    this.workspace.nodes.push(outcomeNode);\n    this.canvas.render();\n    this.tree.render();\n    this.canvas.setActiveNode(outcomeNode.id);\n    this.layout.animateToBowtieLayout(diag.id, { newlyAddedNode: outcomeNode });\n    this.scheduleAutoSave(`Added new Outcome to diagram ${diag.title}`);\n    this.showNotification(`Added new Outcome to diagram \"${diag.title}\"`, 'success');\n    if (this.autoAlignEnabled) {\n      this.triggerLineUpElements(true);\n    }\n  }\n\n  // Add Barrier to active selected node or Top Event\n  addBarrierToCurrentSelection() {\n    if (!this.canEditCurrentDiagram()) {\n      this.showNotification('View Only: You cannot add barriers to this diagram', 'warning');\n      return;\n    }\n    const diag = this.getActiveDiagram();\n    let active = this.getNodeById(this.canvas.activeNodeId);\n    if (!active && diag) {\n      // Intelligently fallback to first threat or top event if nothing is active\n      const diagNodes = this.workspace.nodes.filter(n => n.diagram_id === diag.id);\n      active = diagNodes.find(n => n.node_type === 'threat') || \n               diagNodes.find(n => n.node_type === 'top_event') || \n               diagNodes[0];\n    }\n\n    if (!active) {\n      this.showNotification('Please add or select a Threat or Top Event first to attach a Barrier.', 'info');\n      return;\n    }\n\n    // If an escalation factor or mitigation is currently active, resolve upward to the branch barrier!\n    while (active && (active.node_type === 'escalation_factor' || active.node_type === 'mitigation')) {\n      active = this.getNodeById(active.parent_id);\n    }\n\n    if (!active) {\n      this.showNotification('Please add or select a Threat or Top Event first to attach a Barrier.', 'info');\n      return;\n    }\n\n    const topEvent = this.resolveActiveTopEvent(active.diagram_id) || this.workspace.nodes.find(n => \n      n.id === (this.currentDiagram ? this.currentDiagram.top_event_id : null) || n.node_type === 'top_event'\n    );\n    const isPreEvent = active.node_type === 'threat' || active.node_type === 'threat_barrier' || (topEvent && active.x < topEvent.x);\n    const bType = isPreEvent ? 'threat_barrier' : 'outcome_barrier';\n    const bTitle = isPreEvent ? 'Threat Barrier' : 'Outcome Barrier';\n\n    // Capture undo snapshot BEFORE ANY mutation!\n    this.pushUndoState(`Add ${bTitle} to ${active.title}`, active.diagram_id);\n\n    const newId = `node-barrier-${Date.now().toString(36)}`;\n    const now = new Date().toISOString();\n\n    let newX, newY, newParentId;\n\n    if (active.node_type === 'outcome') {\n      // Adding barrier to selected Outcome: barrier is child of outcome in right-to-left flow\n      newParentId = active.id;\n      newX = active.x;\n      newY = active.y;\n      active.x += 180;\n      const actEl = document.getElementById(`node-${active.id}`);\n      if (actEl) actEl.style.left = `${active.x}px`;\n    } else if (active.node_type === 'outcome_barrier') {\n      const oc = this.getOwningOutcome(active, this.workspace.nodes);\n      newParentId = oc ? oc.id : active.id;\n      const pos = this.layout.calculatePlacementForOutcomeBarrier(active, this.workspace.nodes);\n      newX = pos.x;\n      newY = pos.y;\n\n      if (oc && oc.x <= newX + 135) {\n        oc.x = newX + 180;\n        const actEl = document.getElementById(`node-${oc.id}`);\n        if (actEl) actEl.style.left = `${oc.x}px`;\n      }\n    } else if (active.node_type === 'threat_barrier') {\n      newParentId = active.id;\n      const pos = this.layout.calculatePlacementForThreatBarrier(active, this.workspace.nodes);\n      newX = pos.x;\n      newY = pos.y;\n\n      // Re-parent downstream threat_barrier if one exists\n      const downstreamChild = this.workspace.nodes.find(n =>\n        n.diagram_id === active.diagram_id &&\n        n.parent_id === active.id &&\n        n.node_type === 'threat_barrier'\n      );\n      if (downstreamChild) {\n        downstreamChild.parent_id = newId;\n      }\n    } else {\n      newParentId = active.id;\n      const pos = isPreEvent\n        ? this.layout.calculatePlacementForThreatBarrier(active, this.workspace.nodes)\n        : this.layout.calculatePlacementForOutcomeBarrier(active, this.workspace.nodes);\n      newX = pos.x;\n      newY = pos.y;\n\n      if (active.node_type === 'threat') {\n        // If threat already had a child barrier, re-parent it to newId\n        const downstreamChild = this.workspace.nodes.find(n =>\n          n.diagram_id === active.diagram_id &&\n          n.parent_id === active.id &&\n          n.node_type === 'threat_barrier'\n        );\n        if (downstreamChild) {\n          downstreamChild.parent_id = newId;\n        }\n      } else if (active.node_type === 'top_event') {\n        // If top_event already had a branch head, insert this barrier between topEvent and the branch head\n        const downstreamChild = this.workspace.nodes.find(n =>\n          n.diagram_id === active.diagram_id &&\n          n.parent_id === active.id &&\n          (n.node_type === 'outcome_barrier' || n.node_type === 'outcome')\n        );\n        if (downstreamChild) {\n          downstreamChild.parent_id = newId;\n          if (downstreamChild.x <= newX) {\n            downstreamChild.x = newX + 180;\n            const childEl = document.getElementById(`node-${downstreamChild.id}`);\n            if (childEl) childEl.style.left = `${downstreamChild.x}px`;\n          }\n        }\n      }\n    }\n\n    const barrierNode = {\n      id: newId,\n      diagram_id: active.diagram_id,\n      global_element_id: null,\n      parent_id: newParentId,\n      node_type: bType,\n      title: bTitle,\n      description: '',\n      x: newX,\n      y: newY,\n      width: 135,\n      height: 80,\n      responsible_role: 'Operations',\n      effectiveness: 'High',\n      criticality: 'SCE',\n      notes: '',\n      use_global_metadata: false,\n      created_at: now,\n      updated_at: now,\n    };\n\n    this.workspace.nodes.push(barrierNode);\n    this.fixInvertedOutcomeBarriers();\n    this.canvas.render();\n    this.tree.render();\n    this.canvas.setActiveNode(barrierNode.id);\n    this.layout.animateToBowtieLayout(active.diagram_id, { newlyAddedNode: barrierNode });\n    this.scheduleAutoSave(`Added Barrier to ${active.title}`);\n    this.showNotification(`Added ${bTitle} to ${active.title}`, 'success');\n    if (this.autoAlignEnabled) {\n      this.triggerLineUpElements(true);\n    }\n  }\n\n  // Add Mitigation Control to active Escalation Factor (or barrier's escalation factor)\n  addMitigationToCurrentSelection() {\n    if (!this.canEditCurrentDiagram()) {\n      this.showNotification('View Only: You cannot add mitigations to this diagram', 'warning');\n      return;\n    }\n    let active = this.getNodeById(this.canvas.activeNodeId);\n    if (active && active.node_type === 'escalation_factor') {\n      this.handleAddChildNode(active);\n      return;\n    }\n    if (active && active.node_type === 'mitigation') {\n      const parentEf = this.getNodeById(active.parent_id);\n      if (parentEf) {\n        this.handleAddChildNode(parentEf);\n        return;\n      }\n    }\n    // If a barrier is selected, check if it has an escalation factor\n    if (active && (active.node_type === 'threat_barrier' || active.node_type === 'outcome_barrier')) {\n      const ef = this.workspace.nodes.find(n => n.diagram_id === active.diagram_id && n.parent_id === active.id && n.node_type === 'escalation_factor');\n      if (ef) {\n        this.handleAddChildNode(ef);\n        return;\n      }\n    }\n    // Fallback: look for any escalation factor in active diagram\n    const diag = this.getActiveDiagram();\n    if (diag) {\n      const diagNodes = this.workspace.nodes.filter(n => n.diagram_id === diag.id);\n      const firstEf = diagNodes.find(n => n.node_type === 'escalation_factor');\n      if (firstEf) {\n        this.handleAddChildNode(firstEf);\n        this.showNotification(`Added Mitigation to ${firstEf.title}`, 'success');\n        return;\n      }\n    }\n    this.showNotification('Please add or select an Escalation Factor first before adding a Mitigation.', 'info');\n  }\n\n  handleDeleteNode(nodeId) {\n    if (!this.canEditCurrentDiagram()) {\n      this.showNotification('View Only: You cannot delete elements from this diagram', 'warning');\n      return;\n    }\n    const node = this.getNodeById(nodeId);\n    if (!node) return;\n\n    if (node.node_type === 'hazard' || node.node_type === 'top_event') {\n      const diagNodes = (this.workspace.nodes || []).filter(n => n.diagram_id === node.diagram_id);\n      const hazards = diagNodes.filter(n => n.node_type === 'hazard');\n      const topEvents = diagNodes.filter(n => n.node_type === 'top_event');\n\n      if (node.node_type === 'hazard' && hazards.length <= 1) {\n        alert('Root Hazard and Top Event are required for the diagram and cannot be deleted.');\n        return;\n      }\n      if (node.node_type === 'top_event' && topEvents.length <= 1) {\n        alert('Root Hazard and Top Event are required for the diagram and cannot be deleted.');\n        return;\n      }\n    }\n\n    const isBarrier = node.node_type === 'threat_barrier' || node.node_type === 'outcome_barrier';\n    const diagId = node.diagram_id;\n    const diagNodes = (this.workspace.nodes || []).filter(n => n.diagram_id === diagId);\n    const hasEfs = diagNodes.some(n => n.parent_id === nodeId && n.node_type === 'escalation_factor');\n    const nodeDisplayTitle = this.getNodeDisplayTitle(node);\n\n    let confirmMsg = `Delete '${nodeDisplayTitle}${node.description ? ': ' + node.description : ''}' and all its connected child items?`;\n    if (isBarrier) {\n      confirmMsg = hasEfs\n        ? `Delete '${nodeDisplayTitle}${node.description ? ': ' + node.description : ''}' and its connected escalation factors and mitigations?`\n        : `Delete '${nodeDisplayTitle}${node.description ? ': ' + node.description : ''}'?`;\n    }\n\n    if (!confirm(confirmMsg)) {\n      return;\n    }\n\n    this.pushUndoState(`Delete ${nodeDisplayTitle}`, node.diagram_id);\n\n    // If deleting a hazard, re-link child top events to a remaining hazard so they are not wiped out\n    if (node.node_type === 'hazard') {\n      const remainingHazards = diagNodes.filter(n => n.node_type === 'hazard' && n.id !== nodeId);\n      const newParentId = remainingHazards.length > 0 ? remainingHazards[0].id : null;\n      diagNodes.filter(n => n.parent_id === nodeId && n.node_type === 'top_event').forEach(te => {\n        te.parent_id = newParentId;\n      });\n    }\n\n    let toDelete = new Set();\n    if (isBarrier) {\n      toDelete.add(nodeId);\n\n      // Collect directly connected escalation factors and all their descendants (mitigations)\n      const efs = diagNodes.filter(n => n.parent_id === nodeId && n.node_type === 'escalation_factor');\n      efs.forEach(ef => {\n        toDelete.add(ef.id);\n        const efDescs = this.canvas ? this.canvas.getDescendantIds(ef.id) : [];\n        efDescs.forEach(id => toDelete.add(id));\n      });\n\n      // Find all downstream children along the branch that are NOT escalation factors or mitigations\n      // (e.g. next threat barrier, next outcome barrier, or outcome)\n      const downstreamChildren = diagNodes.filter(n => n.parent_id === nodeId && !toDelete.has(n.id));\n\n      // Re-parent downstream nodes to this barrier's parent\n      let parentId = node.parent_id;\n      if (!parentId) {\n        if (node.node_type === 'threat_barrier') {\n          // Fallback to threat on this branch\n          const threats = diagNodes.filter(n => n.node_type === 'threat');\n          let bestThreat = null;\n          let minDist = Infinity;\n          threats.forEach(t => {\n            const dist = Math.abs((t.y || 0) - (node.y || 0));\n            if (dist < minDist) {\n              minDist = dist;\n              bestThreat = t;\n            }\n          });\n          parentId = bestThreat ? bestThreat.id : null;\n        } else {\n          // Outcome barrier fallback to top event\n          const topEvent = diagNodes.find(n => n.node_type === 'top_event');\n          parentId = topEvent ? topEvent.id : null;\n        }\n      }\n\n      downstreamChildren.forEach(child => {\n        child.parent_id = parentId;\n      });\n    } else {\n      toDelete = new Set([nodeId, ...this.canvas.getDescendantIds(nodeId)]);\n    }\n\n    this.workspace.nodes = this.workspace.nodes.filter(n => !toDelete.has(n.id));\n\n    // Normalize and repair linear branches across the diagram\n    this.fixInvertedOutcomeBarriers();\n\n    // Update diagram root pointers if necessary\n    const diag = (this.workspace.diagrams || []).find(d => d.id === diagId);\n    if (diag) {\n      if (diag.hazard_id === nodeId) {\n        const remainingHaz = (this.workspace.nodes || []).filter(n => n.diagram_id === diagId && n.node_type === 'hazard');\n        if (remainingHaz.length > 0) diag.hazard_id = remainingHaz[0].id;\n      }\n      if (diag.top_event_id === nodeId) {\n        const remainingTop = (this.workspace.nodes || []).filter(n => n.diagram_id === diagId && n.node_type === 'top_event');\n        if (remainingTop.length > 0) diag.top_event_id = remainingTop[0].id;\n      }\n    }\n\n    this.canvas.activeNodeId = null;\n    this.canvas.render();\n    this.tree.render();\n    this.inspector.inspect(null);\n    this.layout.animateToBowtieLayout(diagId);\n    this.scheduleAutoSave(isBarrier ? `Deleted barrier and its escalation controls` : `Deleted node and descendants`);\n    if (this.autoAlignEnabled) {\n      this.triggerLineUpElements(true);\n    }\n  }\n\n  // Sort mitigations horizontally under an escalation factor (or across diagram)\n  sortMitigationsHorizontally(targetEfOrMit = null) {\n    if (!this.canEditCurrentDiagram()) {\n      this.showNotification('View Only: You cannot reorder mitigations on this diagram', 'warning');\n      return false;\n    }\n    const diag = this.getActiveDiagram();\n    if (!diag) return false;\n    const preSnapshot = this.captureWorkspaceSnapshot();\n    const diagNodes = this.workspace.nodes.filter(n => n.diagram_id === diag.id);\n    const topEvent = diagNodes.find(n => n.node_type === 'top_event');\n\n    let targetEf = null;\n    if (targetEfOrMit && typeof targetEfOrMit === 'object') {\n      targetEf = targetEfOrMit.node_type === 'escalation_factor' ? targetEfOrMit : this.getNodeById(targetEfOrMit.parent_id);\n    } else if (typeof targetEfOrMit === 'string') {\n      const node = this.getNodeById(targetEfOrMit);\n      if (node) {\n        targetEf = node.node_type === 'escalation_factor' ? node : this.getNodeById(node.parent_id);\n      }\n    } else if (this.canvas && this.canvas.activeNodeId) {\n      const active = this.getNodeById(this.canvas.activeNodeId);\n      if (active) {\n        targetEf = active.node_type === 'escalation_factor' ? active : this.getNodeById(active.parent_id);\n      }\n    }\n\n    const sortEfMitsHoriz = (ef) => {\n      const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n      if (mits.length === 0) return false;\n      const barrier = this.getNodeById(ef.parent_id);\n      const isThreatSide = barrier \n        ? (barrier.node_type === 'threat_barrier' || (topEvent && barrier.x < topEvent.x))\n        : (topEvent && ef.x < topEvent.x);\n\n      const sorted = [...mits].sort((a, b) => {\n        const timeA = (a.time_to_barrier_sec != null && a.time_to_barrier_sec > 0) ? a.time_to_barrier_sec : (isThreatSide ? -(a.x || 0) : (a.x || 0));\n        const timeB = (b.time_to_barrier_sec != null && b.time_to_barrier_sec > 0) ? b.time_to_barrier_sec : (isThreatSide ? -(b.x || 0) : (b.x || 0));\n        return timeA - timeB;\n      });\n\n      const mitW = 120;\n      const stepX = mitW + 25;\n\n      sorted.forEach((mit, idx) => {\n        mit.y = ef.y;\n        if (isThreatSide) {\n          mit.x = ef.x - 25 - mitW - (idx * stepX);\n        } else {\n          mit.x = ef.x + (ef.width || 130) + 25 + (idx * stepX);\n        }\n        const el = document.getElementById(`node-${mit.id}`);\n        if (el) {\n          el.style.left = `${Math.round(mit.x)}px`;\n          el.style.top = `${Math.round(mit.y)}px`;\n        }\n      });\n\n      // Maintain array order in workspace.nodes\n      const mitIds = new Set(sorted.map(m => m.id));\n      const firstIdx = this.workspace.nodes.findIndex(n => mitIds.has(n.id));\n      if (firstIdx !== -1) {\n        const before = this.workspace.nodes.slice(0, firstIdx).filter(n => !mitIds.has(n.id));\n        const after = this.workspace.nodes.slice(firstIdx).filter(n => !mitIds.has(n.id));\n        this.workspace.nodes = [...before, ...sorted, ...after];\n      }\n      return true;\n    };\n\n    let modified = false;\n    if (targetEf && targetEf.node_type === 'escalation_factor') {\n      modified = sortEfMitsHoriz(targetEf);\n      const parentBarrier = this.getNodeById(targetEf.parent_id);\n      if (parentBarrier) {\n        this.sortEscalationFactorsVertically(parentBarrier);\n      }\n    } else {\n      const efs = diagNodes.filter(n => n.node_type === 'escalation_factor');\n      efs.forEach(ef => {\n        if (sortEfMitsHoriz(ef)) modified = true;\n      });\n      const barriers = diagNodes.filter(n => n.node_type === 'threat_barrier' || n.node_type === 'outcome_barrier');\n      barriers.forEach(b => {\n        this.sortEscalationFactorsVertically(b);\n      });\n    }\n\n    if (modified) {\n      if (preSnapshot) this.pushSnapshotToUndo(preSnapshot, 'Align mitigations horizontally');\n      if (this.tree) this.tree.render();\n      if (this.canvas) {\n        this.canvas.renderConnections();\n      }\n      this.scheduleAutoSave('Aligned mitigations horizontally');\n      this.showNotification('Mitigations aligned horizontally', 'success');\n      if (this.autoAlignEnabled) {\n        this.triggerLineUpElements(true);\n      }\n      return true;\n    } else {\n      this.showNotification('No mitigations to align horizontally', 'info');\n      return false;\n    }\n  }\n\n  // Alias / wrapper for backward compatibility with tests and inspector\n  sortMitigationsVertically(targetEfOrMit = null) {\n    const res = this.sortMitigationsHorizontally(targetEfOrMit);\n    if (this.autoAlignEnabled) {\n      this.triggerLineUpElements(true);\n    }\n    return res;\n  }\n\n  // Sort escalation factors vertically under a barrier (shortest time_to_barrier_sec on top)\n  sortEscalationFactorsVertically(targetBarrierOrEf = null) {\n    if (!this.canEditCurrentDiagram()) {\n      this.showNotification('View Only: You cannot reorder escalation factors on this diagram', 'warning');\n      return false;\n    }\n    const diag = this.getActiveDiagram();\n    if (!diag) return false;\n    const preSnapshot = this.captureWorkspaceSnapshot();\n    const diagNodes = this.workspace.nodes.filter(n => n.diagram_id === diag.id);\n\n    let targetBarrier = null;\n    if (targetBarrierOrEf && typeof targetBarrierOrEf === 'object') {\n      targetBarrier = (targetBarrierOrEf.node_type === 'threat_barrier' || targetBarrierOrEf.node_type === 'outcome_barrier')\n        ? targetBarrierOrEf\n        : this.getNodeById(targetBarrierOrEf.parent_id);\n    } else if (typeof targetBarrierOrEf === 'string') {\n      const node = this.getNodeById(targetBarrierOrEf);\n      if (node) {\n        targetBarrier = (node.node_type === 'threat_barrier' || node.node_type === 'outcome_barrier')\n          ? node\n          : this.getNodeById(node.parent_id);\n      }\n    } else if (this.canvas && this.canvas.activeNodeId) {\n      const active = this.getNodeById(this.canvas.activeNodeId);\n      if (active) {\n        targetBarrier = (active.node_type === 'threat_barrier' || active.node_type === 'outcome_barrier')\n          ? active\n          : this.getNodeById(active.parent_id);\n      }\n    }\n\n    const sortBarrierEfs = (barrier) => {\n      const efs = diagNodes.filter(n => n.parent_id === barrier.id && n.node_type === 'escalation_factor');\n      if (efs.length <= 1) return false;\n\n      const sorted = [...efs].sort((a, b) => {\n        const timeA = (a.time_to_barrier_sec != null && a.time_to_barrier_sec > 0) ? a.time_to_barrier_sec : (a.y || 0);\n        const timeB = (b.time_to_barrier_sec != null && b.time_to_barrier_sec > 0) ? b.time_to_barrier_sec : (b.y || 0);\n        return timeA - timeB;\n      });\n\n      const efX = barrier.x + ((barrier.width || 135) - 130) / 2;\n      let currY = barrier.y + (barrier.height || 80) + 25;\n      const topEvent = diagNodes.find(n => n.node_type === 'top_event');\n\n      sorted.forEach((ef) => {\n        ef.x = efX;\n        ef.y = currY;\n\n        const el = document.getElementById(`node-${ef.id}`);\n        if (el) {\n          el.style.left = `${Math.round(ef.x)}px`;\n          el.style.top = `${Math.round(ef.y)}px`;\n        }\n\n        // Keep child mitigations horizontal and matching the EF's Y coordinate\n        const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n        const isThreatSide = barrier \n          ? (barrier.node_type === 'threat_barrier' || (topEvent && barrier.x < topEvent.x))\n          : (topEvent && ef.x < topEvent.x);\n        const mitW = 120;\n        const stepX = mitW + 25;\n        const sortedMits = [...mits].sort((a, b) => {\n          const timeA = (a.time_to_barrier_sec != null && a.time_to_barrier_sec > 0) ? a.time_to_barrier_sec : (isThreatSide ? -(a.x || 0) : (a.x || 0));\n          const timeB = (b.time_to_barrier_sec != null && b.time_to_barrier_sec > 0) ? b.time_to_barrier_sec : (isThreatSide ? -(b.x || 0) : (b.x || 0));\n          return timeA - timeB;\n        });\n        sortedMits.forEach((mit, idx) => {\n          mit.y = currY;\n          mit.x = isThreatSide ? (ef.x - 25 - mitW - (idx * stepX)) : (ef.x + (ef.width || 130) + 25 + (idx * stepX));\n          const mEl = document.getElementById(`node-${mit.id}`);\n          if (mEl) {\n            mEl.style.left = `${Math.round(mit.x)}px`;\n            mEl.style.top = `${Math.round(mit.y)}px`;\n          }\n        });\n\n        currY += 98;\n      });\n\n      // Maintain array order in workspace.nodes\n      const efIds = new Set(sorted.map(e => e.id));\n      const firstIdx = this.workspace.nodes.findIndex(n => efIds.has(n.id));\n      if (firstIdx !== -1) {\n        const before = this.workspace.nodes.slice(0, firstIdx).filter(n => !efIds.has(n.id));\n        const after = this.workspace.nodes.slice(firstIdx).filter(n => !efIds.has(n.id));\n        this.workspace.nodes = [...before, ...sorted, ...after];\n      }\n      return true;\n    };\n\n    let modified = false;\n    if (targetBarrier && (targetBarrier.node_type === 'threat_barrier' || targetBarrier.node_type === 'outcome_barrier')) {\n      modified = sortBarrierEfs(targetBarrier);\n    } else {\n      const barriers = diagNodes.filter(n => n.node_type === 'threat_barrier' || n.node_type === 'outcome_barrier');\n      barriers.forEach(b => {\n        if (sortBarrierEfs(b)) modified = true;\n      });\n    }\n\n    if (modified) {\n      if (preSnapshot) this.pushSnapshotToUndo(preSnapshot, 'Sort escalation factors vertically');\n      if (this.tree) this.tree.render();\n      if (this.canvas) {\n        this.canvas.renderConnections();\n      }\n      this.scheduleAutoSave('Sorted escalation factors vertically');\n      this.showNotification('Escalation factors sorted vertically', 'success');\n      if (this.autoAlignEnabled) {\n        this.triggerLineUpElements(true);\n      }\n      return true;\n    } else {\n      this.showNotification('No multiple escalation factors to sort vertically', 'info');\n      return false;\n    }\n  }\n\n  // Order barriers along a branch by time to barrier (shortest on the left)\n  orderBarriersByTime(targetNodeOrDiag = null) {\n    if (!this.canEditCurrentDiagram()) {\n      this.showNotification('View Only: You cannot reorder barriers on this diagram', 'warning');\n      return;\n    }\n    const diag = this.getActiveDiagram();\n    if (!diag) return;\n    const preSnapshot = this.captureWorkspaceSnapshot();\n    const diagNodes = this.workspace.nodes.filter(n => n.diagram_id === diag.id);\n    const topEvent = diagNodes.find(n => n.node_type === 'top_event');\n\n    let targetNode = null;\n    if (targetNodeOrDiag && typeof targetNodeOrDiag === 'object') {\n      targetNode = targetNodeOrDiag;\n    } else if (typeof targetNodeOrDiag === 'string') {\n      targetNode = this.getNodeById(targetNodeOrDiag);\n    } else if (this.canvas && this.canvas.activeNodeId) {\n      targetNode = this.getNodeById(this.canvas.activeNodeId);\n    }\n\n    if (targetNode && (targetNode.node_type === 'mitigation' || targetNode.node_type === 'escalation_factor')) {\n      if (targetNode.node_type === 'escalation_factor') {\n        const parentBarrier = this.getNodeById(targetNode.parent_id);\n        const siblingEfs = parentBarrier\n          ? diagNodes.filter(n => n.parent_id === parentBarrier.id && n.node_type === 'escalation_factor')\n          : [];\n        if (siblingEfs.length > 1) {\n          const efSorted = this.sortEscalationFactorsVertically(parentBarrier);\n          const mits = diagNodes.filter(n => n.parent_id === targetNode.id && n.node_type === 'mitigation');\n          if (mits.length > 1) {\n            this.sortMitigationsVertically(targetNode);\n          }\n          return efSorted;\n        }\n      }\n      return this.sortMitigationsVertically(targetNode);\n    }\n\n    // Sort threat barriers: shortest time_to_barrier_sec is on far left (closest to threat)\n    const sortThreatBarriers = (threat) => {\n      const barriers = this.layout ? this.layout.getLinearChainForThreat(threat, diagNodes) : [];\n      if (barriers.length <= 1) return false;\n\n      const sorted = [...barriers].sort((a, b) => {\n        const timeA = (a.time_to_barrier_sec != null && a.time_to_barrier_sec > 0) ? a.time_to_barrier_sec : 3.0;\n        const timeB = (b.time_to_barrier_sec != null && b.time_to_barrier_sec > 0) ? b.time_to_barrier_sec : 3.0;\n        return timeA - timeB;\n      });\n\n      const slots = [...barriers].map(b => b.x).sort((a, b) => a - b);\n\n      sorted.forEach((barrier, idx) => {\n        barrier.parent_id = (idx === 0) ? threat.id : sorted[idx - 1].id;\n        barrier.x = slots[idx];\n\n        const efs = diagNodes.filter(n => n.parent_id === barrier.id && n.node_type === 'escalation_factor');\n        efs.forEach(ef => {\n          ef.x = barrier.x + ((barrier.width || 135) - (ef.width || 130)) / 2;\n        });\n      });\n\n      // Reorder in workspace.nodes to maintain consistent array order\n      const barrierIds = new Set(sorted.map(b => b.id));\n      const firstIdx = this.workspace.nodes.findIndex(n => barrierIds.has(n.id));\n      if (firstIdx !== -1) {\n        const before = this.workspace.nodes.slice(0, firstIdx).filter(n => !barrierIds.has(n.id));\n        const after = this.workspace.nodes.slice(firstIdx).filter(n => !barrierIds.has(n.id));\n        this.workspace.nodes = [...before, ...sorted, ...after];\n      }\n      return true;\n    };\n\n    // Sort outcome barriers: shortest time_to_barrier_sec is on far left (closest to topEvent)\n    const sortOutcomeBarriers = (outcomeOrBarrier) => {\n      const owningTe = this.layout\n        ? this.layout.getTopEventForNode(outcomeOrBarrier, diagNodes)\n        : (topEvent || diagNodes.find(n => n.node_type === 'top_event'));\n      const rightBranches = (this.layout && owningTe)\n        ? this.layout.getClusterRightBranches(owningTe, diagNodes)\n        : [];\n\n      let chain = null;\n      for (const branch of rightBranches) {\n        if (branch.chain.some(n => n.id === outcomeOrBarrier.id)) {\n          chain = branch.chain;\n          break;\n        }\n      }\n\n      if (!chain || chain.length === 0) return false;\n      const barriers = chain.filter(n => n.node_type === 'outcome_barrier');\n      const outcome = chain.find(n => n.node_type === 'outcome');\n      if (barriers.length <= 1) return false;\n\n      const sorted = [...barriers].sort((a, b) => {\n        const timeA = (a.time_to_barrier_sec != null && a.time_to_barrier_sec > 0) ? a.time_to_barrier_sec : 3.0;\n        const timeB = (b.time_to_barrier_sec != null && b.time_to_barrier_sec > 0) ? b.time_to_barrier_sec : 3.0;\n        return timeA - timeB;\n      });\n\n      const k = sorted.length;\n      sorted.forEach((barrier, idx) => {\n        barrier.x = slots[idx];\n\n        const efs = diagNodes.filter(n => n.parent_id === barrier.id && n.node_type === 'escalation_factor');\n        efs.forEach(ef => {\n          ef.x = barrier.x + ((barrier.width || 135) - (ef.width || 130)) / 2;\n        });\n      });\n\n      if (outcome && k > 0) {\n        outcome.parent_id = owningTe ? owningTe.id : null;\n        sorted[k - 1].parent_id = outcome.id;\n        for (let i = k - 2; i >= 0; i--) {\n          sorted[i].parent_id = sorted[i + 1].id;\n        }\n      } else if (k > 0) {\n        sorted[0].parent_id = owningTe ? owningTe.id : null;\n        for (let i = 1; i < k; i++) {\n          sorted[i].parent_id = sorted[i - 1].id;\n        }\n      }\n\n      // Reorder in workspace.nodes to maintain consistent array order\n      const barrierIds = new Set(sorted.map(b => b.id));\n      const firstIdx = this.workspace.nodes.findIndex(n => barrierIds.has(n.id));\n      if (firstIdx !== -1) {\n        const before = this.workspace.nodes.slice(0, firstIdx).filter(n => !barrierIds.has(n.id));\n        const after = this.workspace.nodes.slice(firstIdx).filter(n => !barrierIds.has(n.id));\n        this.workspace.nodes = [...before, ...sorted, ...after];\n      }\n      return true;\n    };\n\n    let modified = false;\n\n    if (targetNode) {\n      if (targetNode.node_type === 'threat') {\n        modified = sortThreatBarriers(targetNode);\n      } else if (targetNode.node_type === 'outcome') {\n        modified = sortOutcomeBarriers(targetNode);\n      } else if (targetNode.node_type === 'threat_barrier') {\n        const threats = diagNodes.filter(n => n.node_type === 'threat');\n        for (const t of threats) {\n          const c = this.layout ? this.layout.getLinearChainForThreat(t, diagNodes) : [];\n          if (c.some(b => b.id === targetNode.id)) {\n            modified = sortThreatBarriers(t);\n            break;\n          }\n        }\n      } else if (targetNode.node_type === 'outcome_barrier') {\n        modified = sortOutcomeBarriers(targetNode);\n      }\n    }\n\n    if (!modified) {\n      // Sort all branches across active diagram\n      const threats = diagNodes.filter(n => n.node_type === 'threat');\n      threats.forEach(t => {\n        if (sortThreatBarriers(t)) modified = true;\n      });\n\n      const topEvents = diagNodes.filter(n => n.node_type === 'top_event');\n      topEvents.forEach(te => {\n        const branches = this.layout ? this.layout.getClusterRightBranches(te, diagNodes) : [];\n        branches.forEach(b => {\n          if (b.chain.length > 0 && sortOutcomeBarriers(b.chain[0])) modified = true;\n        });\n      });\n\n      // Also sort mitigations vertically for escalation factors with multiple mitigations\n      const efs = diagNodes.filter(n => n.node_type === 'escalation_factor');\n      efs.forEach(ef => {\n        const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n        if (mits.length > 1) {\n          const barrier = this.getNodeById(ef.parent_id);\n          const isThreatSide = barrier \n            ? (barrier.node_type === 'threat_barrier' || (topEvent && barrier.x < topEvent.x))\n            : (topEvent && ef.x < topEvent.x);\n\n          const sorted = [...mits].sort((a, b) => {\n            const timeA = (a.time_to_barrier_sec != null && a.time_to_barrier_sec > 0) ? a.time_to_barrier_sec : (a.y || 0);\n            const timeB = (b.time_to_barrier_sec != null && b.time_to_barrier_sec > 0) ? b.time_to_barrier_sec : (b.y || 0);\n            return timeA - timeB;\n          });\n\n          const mitW = 120;\n          const slot0X = isThreatSide ? (ef.x - 25 - mitW) : (ef.x + (ef.width || 130) + 25);\n          const slot0Y = ef.y;\n          const stepY = 95;\n\n          sorted.forEach((mit, idx) => {\n            mit.x = slot0X;\n            mit.y = slot0Y + idx * stepY;\n            const el = document.getElementById(`node-${mit.id}`);\n            if (el) {\n              el.style.left = `${Math.round(mit.x)}px`;\n              el.style.top = `${Math.round(mit.y)}px`;\n            }\n          });\n\n          const mitIds = new Set(sorted.map(m => m.id));\n          const firstIdx = this.workspace.nodes.findIndex(n => mitIds.has(n.id));\n          if (firstIdx !== -1) {\n            const before = this.workspace.nodes.slice(0, firstIdx).filter(n => !mitIds.has(n.id));\n            const after = this.workspace.nodes.slice(firstIdx).filter(n => !mitIds.has(n.id));\n            this.workspace.nodes = [...before, ...sorted, ...after];\n          }\n          modified = true;\n        }\n      });\n\n      // Also sort escalation factors vertically for barriers with multiple escalation factors\n      const barriers = diagNodes.filter(n => n.node_type === 'threat_barrier' || n.node_type === 'outcome_barrier');\n      barriers.forEach(b => {\n        const efs = diagNodes.filter(n => n.parent_id === b.id && n.node_type === 'escalation_factor');\n        if (efs.length > 1) {\n          if (this.sortEscalationFactorsVertically(b)) modified = true;\n        }\n      });\n    }\n\n    if (modified) {\n      if (preSnapshot) this.pushSnapshotToUndo(preSnapshot, 'Order barriers by time');\n      if (this.tree) this.tree.render();\n      this.layout.balanceDiagram(diag.id);\n      this.canvas.renderConnections();\n      this.scheduleAutoSave('Ordered barriers by time to barrier');\n      this.showNotification('Barriers ordered by time (shortest on the left)!', 'success');\n      if (this.autoAlignEnabled) {\n        this.triggerLineUpElements(true);\n      }\n    } else {\n      this.showNotification('No multiple barriers found to order on this branch.', 'info');\n    }\n  }\n\n  insertGlobalElementInstance(ge) {\n    if (!this.currentDiagram) return;\n    if (!this.canEditCurrentDiagram()) {\n      this.showNotification('View Only: You cannot insert elements into this diagram', 'warning');\n      return;\n    }\n    const diag = this.getActiveDiagram();\n    if (!diag) return;\n    const diagNodes = this.workspace.nodes.filter(n => n.diagram_id === diag.id);\n    const topEvent = this.resolveActiveTopEvent(diag.id) || diagNodes.find(n => n.node_type === 'top_event');\n    const now = new Date().toISOString();\n    const newId = `node-ge-${Date.now().toString(36)}`;\n\n    let newX, newY, newParentId;\n\n    if (ge.node_type === 'threat_barrier') {\n      // Find appropriate threat branch (either from active selection or first threat/threat_barrier)\n      let active = this.getNodeById(this.canvas ? this.canvas.activeNodeId : null);\n      while (active && (active.node_type === 'escalation_factor' || active.node_type === 'mitigation')) {\n        active = this.getNodeById(active.parent_id);\n      }\n      let targetBranch = null;\n      if (active && active.diagram_id === diag.id && (active.node_type === 'threat' || active.node_type === 'threat_barrier')) {\n        targetBranch = active;\n      } else {\n        // Fallback: first threat or first threat barrier in this diagram\n        targetBranch = diagNodes.find(n => n.node_type === 'threat_barrier') || diagNodes.find(n => n.node_type === 'threat');\n      }\n\n      if (!targetBranch) {\n        newParentId = topEvent ? topEvent.id : null;\n        newX = topEvent ? topEvent.x - 220 : 300;\n        newY = topEvent ? topEvent.y : 300;\n      } else {\n        newParentId = targetBranch.id;\n        const pos = this.layout.calculatePlacementForThreatBarrier(targetBranch, this.workspace.nodes);\n        newX = pos.x;\n        newY = pos.y;\n\n        // Splice into linear chain: reparent any downstream threat_barrier child\n        const downstreamChild = this.workspace.nodes.find(n =>\n          n.diagram_id === diag.id &&\n          n.parent_id === targetBranch.id &&\n          n.node_type === 'threat_barrier'\n        );\n        if (downstreamChild) {\n          downstreamChild.parent_id = newId;\n        }\n      }\n    } else if (ge.node_type === 'outcome_barrier') {\n      let active = this.getNodeById(this.canvas ? this.canvas.activeNodeId : null);\n      while (active && (active.node_type === 'escalation_factor' || active.node_type === 'mitigation')) {\n        active = this.getNodeById(active.parent_id);\n      }\n      let targetBranch = null;\n      if (active && active.diagram_id === diag.id && (active.node_type === 'outcome' || active.node_type === 'outcome_barrier')) {\n        targetBranch = active;\n      } else {\n        targetBranch = diagNodes.find(n => n.node_type === 'outcome_barrier') || diagNodes.find(n => n.node_type === 'outcome');\n      }\n\n      if (!targetBranch) {\n        newParentId = topEvent ? topEvent.id : null;\n        newX = topEvent ? topEvent.x + 220 : 800;\n        newY = topEvent ? topEvent.y : 300;\n      } else if (targetBranch.node_type === 'outcome') {\n        newParentId = targetBranch.id;\n        newX = targetBranch.x;\n        newY = targetBranch.y;\n        targetBranch.x += 180;\n        const actEl = document.getElementById(`node-${targetBranch.id}`);\n        if (actEl) actEl.style.left = `${targetBranch.x}px`;\n      } else {\n        const oc = this.getOwningOutcome(targetBranch, diagNodes);\n        newParentId = oc ? oc.id : targetBranch.id;\n        const pos = this.layout.calculatePlacementForOutcomeBarrier(targetBranch, this.workspace.nodes);\n        newX = pos.x;\n        newY = pos.y;\n        if (oc && oc.x <= newX + 135) {\n          oc.x = newX + 180;\n          const actEl = document.getElementById(`node-${oc.id}`);\n          if (actEl) actEl.style.left = `${oc.x}px`;\n        }\n      }\n    } else if (ge.node_type === 'threat') {\n      const pos = this.layout.calculateNewThreatPlacement(diag.id, this.workspace.nodes, topEvent);\n      newX = pos.x;\n      newY = pos.y;\n      newParentId = topEvent ? topEvent.id : null;\n    } else if (ge.node_type === 'outcome') {\n      const pos = this.layout.calculateNewOutcomePlacement(diag.id, this.workspace.nodes, topEvent);\n      newX = pos.x;\n      newY = pos.y;\n      newParentId = topEvent ? topEvent.id : null;\n    } else {\n      newParentId = topEvent ? topEvent.id : null;\n      newX = topEvent ? topEvent.x - 220 : 500;\n      newY = topEvent ? topEvent.y : 300;\n    }\n\n    const customCat = ge.category || ge.item_category;\n    const defaultTitle = this.getNodeDisplayTitle ? this.getNodeDisplayTitle({ node_type: ge.node_type }) : (ge.node_type === 'threat_barrier' ? 'Threat Barrier' : ge.name);\n\n    const newNode = {\n      id: newId,\n      diagram_id: diag.id,\n      global_element_id: ge.id,\n      parent_id: newParentId,\n      node_type: ge.node_type,\n      title: customCat || defaultTitle,\n      title_customized: !!customCat,\n      description: ge.name,\n      x: newX,\n      y: newY,\n      width: 135,\n      height: 80,\n      responsible_role: ge.responsible_role || null,\n      effectiveness: ge.effectiveness || null,\n      criticality: ge.criticality || null,\n      notes: ge.description || '',\n      use_global_metadata: true,\n      created_at: now,\n      updated_at: now,\n    };\n\n    this.pushUndoState(`Insert master element '${ge.name}'`, diag.id);\n    this.workspace.nodes.push(newNode);\n    this.layout.balanceDiagram(diag.id);\n    this.canvas.render();\n    this.tree.render();\n    this.canvas.setActiveNode(newNode.id);\n    this.layout.animateToBowtieLayout(diag.id, { newlyAddedNode: newNode });\n    this.canvas.renderConnections();\n    if (this.autoAlignEnabled) {\n      this.triggerLineUpElements(true);\n    }\n    this.showNotification(`Inserted instance of master element '${ge.name}' into diagram!`, 'success');\n    this.scheduleAutoSave(`Inserted instance of master element '${ge.name}'`);\n  }\n\n  // Create an entirely new diagram on the canvas\n  createNewDiagram(title, desc, hazardName, topEventName) {\n    if (!this.isInstanceLicensedForEdit()) {\n      this.showUnlicensedPrompt();\n      return;\n    }\n    const diagId = `diag-${Date.now().toString(36)}`;\n    const now = new Date().toISOString();\n    const email = this.currentUser ? this.currentUser.email : 'djb@bowtierisk.com';\n\n    const hazardId = `node-haz-${Date.now().toString(36)}`;\n    const topEventId = `node-top-${Date.now().toString(36)}`;\n\n    const activeFolderId = (this.currentView === 'drive' && this.documents && this.documents.currentFolderId)\n      ? this.documents.currentFolderId\n      : null;\n\n    const newDiagram = {\n      id: diagId,\n      title: title || 'New Bowtie Assessment',\n      description: desc || '',\n      folder_id: activeFolderId,\n      hazard_id: hazardId,\n      top_event_id: topEventId,\n      created_by: email,\n      created_at: now,\n      updated_at: now,\n    };\n\n    const hazardNode = {\n      id: hazardId,\n      diagram_id: diagId,\n      global_element_id: null,\n      parent_id: null,\n      node_type: 'hazard',\n      title: 'Hazard',\n      description: (hazardName || '').trim(),\n      x: 630,\n      y: 190,\n      width: 150,\n      height: 90,\n      responsible_role: 'Operations',\n      effectiveness: null,\n      criticality: 'Major Hazard',\n      notes: '',\n      use_global_metadata: false,\n      created_at: now,\n      updated_at: now,\n    };\n\n    const topEventNode = {\n      id: topEventId,\n      diagram_id: diagId,\n      global_element_id: null,\n      parent_id: hazardId,\n      node_type: 'top_event',\n      title: 'Top event',\n      description: (topEventName || '').trim(),\n      x: 640,\n      y: 350,\n      width: 130,\n      height: 130,\n      responsible_role: 'Asset Manager',\n      effectiveness: null,\n      criticality: 'Level 4 Severity',\n      notes: '',\n      use_global_metadata: false,\n      created_at: now,\n      updated_at: now,\n    };\n\n    this.workspace.diagrams.push(newDiagram);\n    this.workspace.nodes.push(hazardNode, topEventNode);\n    this.undoStacks[diagId] = [];\n    this.redoStacks[diagId] = [];\n    this.openDiagram(diagId);\n    this.initDiagramSelector();\n    this.updateUndoRedoUI();\n    if (this.canvas) {\n      this.canvas.zoomFit();\n    }\n    this.showNotification(`Created new diagram '${title}'!`, 'success');\n    this.scheduleAutoSave(`Created new diagram ${title}`);\n  }\n\n  // Add a new Hazard and Top Event cluster to the active diagram\n  addHazardAndTopEventToCurrentDiagram(hazardName, topEventName) {\n    if (!this.isInstanceLicensedForEdit()) {\n      this.showUnlicensedPrompt();\n      return;\n    }\n    const diag = this.getActiveDiagram();\n    if (!diag) return;\n\n    const diagNodes = (this.workspace.nodes || []).filter(n => n.diagram_id === diag.id);\n    let maxY = 350;\n    let centerX = 640;\n    if (diagNodes.length > 0) {\n      maxY = Math.max(...diagNodes.map(n => (n.y || 0) + (n.height || 80)));\n      const topEvent = diagNodes.find(n => n.node_type === 'top_event');\n      if (topEvent) centerX = topEvent.x || 640;\n    }\n\n    const newHazY = maxY + 100;\n    const newTopY = newHazY + 140;\n\n    const hazardId = `node-haz-${Date.now().toString(36)}`;\n    const topEventId = `node-top-${Date.now().toString(36)}`;\n    const now = new Date().toISOString();\n\n    const hazardNode = {\n      id: hazardId,\n      diagram_id: diag.id,\n      global_element_id: null,\n      parent_id: null,\n      node_type: 'hazard',\n      title: 'Hazard',\n      description: (hazardName || '').trim(),\n      x: centerX - 10,\n      y: newHazY,\n      width: 150,\n      height: 90,\n      responsible_role: 'Operations',\n      effectiveness: null,\n      criticality: 'Major Hazard',\n      notes: '',\n      use_global_metadata: false,\n      created_at: now,\n      updated_at: now,\n    };\n\n    const topEventNode = {\n      id: topEventId,\n      diagram_id: diag.id,\n      global_element_id: null,\n      parent_id: hazardId,\n      node_type: 'top_event',\n      title: 'Top event',\n      description: (topEventName || '').trim(),\n      x: centerX,\n      y: newTopY,\n      width: 130,\n      height: 130,\n      responsible_role: 'Asset Manager',\n      effectiveness: null,\n      criticality: 'Level 4 Severity',\n      notes: '',\n      use_global_metadata: false,\n      created_at: now,\n      updated_at: now,\n    };\n\n    this.pushUndoState(`Add Hazard & Top Event to ${diag.title}`, diag.id);\n    this.workspace.nodes.push(hazardNode, topEventNode);\n    this.canvas.render();\n    this.tree.render();\n    this.canvas.setActiveNode(topEventId);\n    this.canvas.centerOnNode(topEventId);\n    this.showNotification(`Added Hazard & Top Event to \"${diag.title}\"!`, 'success');\n    this.scheduleAutoSave(`Added Hazard & Top Event cluster to diagram ${diag.title}`);\n    if (this.autoAlignEnabled) {\n      this.triggerLineUpElements(true);\n    }\n  }\n\n  // ==========================================================================\n  // MODALS & EVENT HANDLERS\n  // ==========================================================================\n  initModals() {\n    if (this._modalsInitialized) return;\n    this._modalsInitialized = true;\n    // Helper to dismiss modal cleanly and prevent floating browser tooltips\n    const closeModalCleanly = (modal) => {\n      if (!modal) return;\n      if (document.activeElement && typeof document.activeElement.blur === 'function') {\n        document.activeElement.blur();\n      }\n      modal.classList.remove('show');\n      const form = modal.querySelector('form');\n      if (form && typeof form.reset === 'function') {\n        form.reset();\n      }\n      modal.querySelectorAll('.input-error').forEach(el => el.classList.remove('input-error'));\n    };\n\n    // Generic modal close buttons\n    document.querySelectorAll('[data-close]').forEach(btn => {\n      btn.addEventListener('click', () => {\n        const modalId = btn.getAttribute('data-close');\n        const modal = document.getElementById(modalId);\n        closeModalCleanly(modal);\n      });\n    });\n\n    // Close on overlay backdrop click\n    document.querySelectorAll('.modal-overlay').forEach(modal => {\n      modal.addEventListener('click', (e) => {\n        if (e.target === modal) {\n          closeModalCleanly(modal);\n        }\n      });\n    });\n\n    // Close on Escape key\n    window.addEventListener('keydown', (e) => {\n      if (e.key === 'Escape' || e.keyCode === 27) {\n        document.querySelectorAll('.modal-overlay.show').forEach(m => closeModalCleanly(m));\n      }\n    });\n\n    // Cleanly notify presence departure on window unload or page navigation\n    const handleUnloadPresence = () => {\n      if (this.currentDiagram && this.currentDiagram.id) {\n        this.leaveCurrentDiagramPresence(this.currentDiagram.id);\n      }\n    };\n    window.addEventListener('beforeunload', handleUnloadPresence);\n    window.addEventListener('pagehide', handleUnloadPresence);\n\n    // Auto-remove input-error highlight when user edits field\n    document.addEventListener('input', (e) => {\n      if (e.target && e.target.classList && e.target.classList.contains('input-error')) {\n        e.target.classList.remove('input-error');\n      }\n    });\n    document.addEventListener('change', (e) => {\n      if (e.target && e.target.classList && e.target.classList.contains('input-error')) {\n        e.target.classList.remove('input-error');\n      }\n    });\n\n    // Left & Right Pane Desktop Toggles & Edge Handles\n    const btnCollapseLeft = document.getElementById('btnCollapseLeft');\n    const leftPane = document.getElementById('leftPane');\n    if (btnCollapseLeft && leftPane) {\n      btnCollapseLeft.addEventListener('click', () => {\n        if (window.innerWidth <= 900 && leftPane.classList.contains('drawer-open')) {\n          this.closeMobileDrawers();\n        } else {\n          leftPane.classList.toggle('collapsed');\n          this.updateSidebarStateUI();\n        }\n      });\n    }\n\n    const btnCollapseRight = document.getElementById('btnCollapseRight');\n    const rightPane = document.getElementById('rightPane');\n    if (btnCollapseRight && rightPane) {\n      btnCollapseRight.addEventListener('click', () => {\n        if (window.innerWidth <= 900 && rightPane.classList.contains('drawer-open')) {\n          this.closeMobileDrawers();\n        } else {\n          rightPane.classList.toggle('collapsed');\n          this.updateSidebarStateUI();\n        }\n      });\n    }\n\n    // Edge Restore Tab Handles (when collapsed or closed)\n    const btnEdgeToggleLeft = document.getElementById('btnEdgeToggleLeft');\n    if (btnEdgeToggleLeft && leftPane) {\n      btnEdgeToggleLeft.addEventListener('click', () => {\n        if (window.innerWidth <= 900) {\n          leftPane.classList.remove('collapsed');\n          leftPane.classList.add('drawer-open');\n          const mobileBackdrop = document.getElementById('mobileDrawerBackdrop');\n          if (mobileBackdrop) mobileBackdrop.classList.add('show');\n          this._drawerOpenedTime = Date.now();\n        } else {\n          leftPane.classList.remove('collapsed');\n        }\n        this.updateSidebarStateUI();\n      });\n    }\n\n    const btnEdgeToggleRight = document.getElementById('btnEdgeToggleRight');\n    if (btnEdgeToggleRight && rightPane) {\n      btnEdgeToggleRight.addEventListener('click', () => {\n        if (window.innerWidth <= 900) {\n          rightPane.classList.remove('collapsed');\n          rightPane.classList.add('drawer-open');\n          const mobileBackdrop = document.getElementById('mobileDrawerBackdrop');\n          if (mobileBackdrop) mobileBackdrop.classList.add('show');\n          this._drawerOpenedTime = Date.now();\n        } else {\n          rightPane.classList.remove('collapsed');\n        }\n        this.updateSidebarStateUI();\n      });\n    }\n\n    // Top Navigation Sidebar Toggle Icons (safe fallback if present)\n    const btnSidebarToggleLeft = document.getElementById('btnSidebarToggleLeft');\n    if (btnSidebarToggleLeft && leftPane) {\n      btnSidebarToggleLeft.addEventListener('click', () => {\n        if (window.innerWidth <= 900) {\n          const isOpen = leftPane.classList.contains('drawer-open');\n          this.closeMobileDrawers();\n          if (!isOpen) {\n            leftPane.classList.remove('collapsed');\n            leftPane.classList.add('drawer-open');\n            const mobileBackdrop = document.getElementById('mobileDrawerBackdrop');\n            if (mobileBackdrop) mobileBackdrop.classList.add('show');\n            this._drawerOpenedTime = Date.now();\n          }\n        } else {\n          leftPane.classList.toggle('collapsed');\n          this.updateSidebarStateUI();\n        }\n      });\n    }\n\n    const btnSidebarToggleRight = document.getElementById('btnSidebarToggleRight');\n    if (btnSidebarToggleRight && rightPane) {\n      btnSidebarToggleRight.addEventListener('click', () => {\n        if (window.innerWidth <= 900) {\n          const isOpen = rightPane.classList.contains('drawer-open');\n          this.closeMobileDrawers();\n          if (!isOpen) {\n            rightPane.classList.remove('collapsed');\n            rightPane.classList.add('drawer-open');\n            const mobileBackdrop = document.getElementById('mobileDrawerBackdrop');\n            if (mobileBackdrop) mobileBackdrop.classList.add('show');\n            this._drawerOpenedTime = Date.now();\n          }\n        } else {\n          rightPane.classList.toggle('collapsed');\n          this.updateSidebarStateUI();\n        }\n      });\n    }\n\n    // Mobile Drawer Toggles & Backdrop\n    const btnMobileToggleLeft = document.getElementById('btnMobileToggleLeft');\n    const btnMobileToggleRight = document.getElementById('btnMobileToggleRight');\n    const mobileBackdrop = document.getElementById('mobileDrawerBackdrop');\n\n    if (btnMobileToggleLeft && leftPane) {\n      btnMobileToggleLeft.addEventListener('click', () => {\n        const isOpen = leftPane.classList.contains('drawer-open');\n        this.closeMobileDrawers();\n        if (!isOpen) {\n          leftPane.classList.remove('collapsed');\n          leftPane.classList.add('drawer-open');\n          if (mobileBackdrop) mobileBackdrop.classList.add('show');\n          this._drawerOpenedTime = Date.now();\n        }\n      });\n    }\n\n    if (btnMobileToggleRight && rightPane) {\n      btnMobileToggleRight.addEventListener('click', () => {\n        const isOpen = rightPane.classList.contains('drawer-open');\n        this.closeMobileDrawers();\n        if (!isOpen) {\n          rightPane.classList.remove('collapsed');\n          rightPane.classList.add('drawer-open');\n          if (mobileBackdrop) mobileBackdrop.classList.add('show');\n          this._drawerOpenedTime = Date.now();\n        }\n      });\n    }\n\n    if (mobileBackdrop) {\n      const handleBackdropDismiss = (e) => {\n        if (Date.now() - (this._drawerOpenedTime || 0) < 250) return;\n        this.closeMobileDrawers();\n      };\n      mobileBackdrop.addEventListener('click', handleBackdropDismiss);\n      mobileBackdrop.addEventListener('touchend', (e) => {\n        if (Date.now() - (this._drawerOpenedTime || 0) < 250) return;\n        e.preventDefault();\n        this.closeMobileDrawers();\n      }, { passive: false });\n    }\n\n    // Mobile Burger Menu & Drawer Panel\n    const btnMobileBurger = document.getElementById('btnMobileBurgerMenu');\n    const mobileMenuPanel = document.getElementById('mobileMenuPanel');\n    const btnCloseMobileMenu = document.getElementById('btnCloseMobileMenu');\n    const mobileMenuBackdrop = document.getElementById('mobileMenuBackdrop');\n\n    const openMobileMenu = () => {\n      if (!mobileMenuPanel) return;\n      mobileMenuPanel.style.display = 'flex';\n      this.updateAutoAlignUI();\n      const saveStatusText = document.querySelector('#saveStatus .status-text');\n      const mobileStatusText = document.getElementById('mobileMenuStatusText');\n      if (saveStatusText && mobileStatusText) {\n        mobileStatusText.textContent = saveStatusText.textContent;\n      }\n      const zoomDisplay = document.getElementById('zoomDisplay');\n      const mobileZoomDisplay = document.getElementById('mobileZoomDisplay');\n      if (zoomDisplay && mobileZoomDisplay) {\n        mobileZoomDisplay.textContent = zoomDisplay.textContent;\n      }\n    };\n\n    const closeMobileMenu = () => {\n      if (mobileMenuPanel) mobileMenuPanel.style.display = 'none';\n    };\n\n    if (btnMobileBurger) {\n      btnMobileBurger.addEventListener('click', openMobileMenu);\n    }\n    if (btnCloseMobileMenu) {\n      btnCloseMobileMenu.addEventListener('click', closeMobileMenu);\n    }\n    if (mobileMenuBackdrop) {\n      mobileMenuBackdrop.addEventListener('click', closeMobileMenu);\n    }\n\n    // Connect mobile menu items to trigger main menu actions and dismiss\n    const connectMobileItem = (id, action) => {\n      const el = document.getElementById(id);\n      if (el) {\n        el.addEventListener('click', (e) => {\n          e.stopPropagation();\n          closeMobileMenu();\n          action();\n        });\n      }\n    };\n\n    connectMobileItem('mobileBtnShare', () => {\n      const btn = document.getElementById('btnOpenShare');\n      if (btn) btn.click();\n    });\n\n    connectMobileItem('mobileBtnHistory', () => {\n      this.toggleDiagramHistory();\n    });\n\n    connectMobileItem('mobileBtnSituationRunner', () => {\n      const btn = document.getElementById('btnToggleSituationRunner');\n      if (btn) btn.click();\n    });\n\n    connectMobileItem('mobileBtnOrderBarriers', () => {\n      const btn = document.getElementById('btnOrderBarriersTime');\n      if (btn) btn.click();\n    });\n\n    connectMobileItem('mobileBtnAutoBalance', () => {\n      const btn = document.getElementById('btnAutoBalance');\n      if (btn) btn.click();\n    });\n\n    connectMobileItem('mobileBtnToggleAutoAlign', () => {\n      this.toggleAutoAlign();\n    });\n\n    connectMobileItem('mobileBtnCostAnalysis', () => {\n      const btn = document.getElementById('btnOpenCostAnalysis');\n      if (btn) btn.click();\n    });\n\n    const mobileBtnZoomOut = document.getElementById('mobileBtnZoomOut');\n    if (mobileBtnZoomOut) {\n      mobileBtnZoomOut.addEventListener('click', () => {\n        const btn = document.getElementById('btnZoomOut');\n        if (btn) btn.click();\n        const zd = document.getElementById('zoomDisplay');\n        const mzd = document.getElementById('mobileZoomDisplay');\n        if (zd && mzd) mzd.textContent = zd.textContent;\n      });\n    }\n\n    const mobileBtnZoomIn = document.getElementById('mobileBtnZoomIn');\n    if (mobileBtnZoomIn) {\n      mobileBtnZoomIn.addEventListener('click', () => {\n        const btn = document.getElementById('btnZoomIn');\n        if (btn) btn.click();\n        const zd = document.getElementById('zoomDisplay');\n        const mzd = document.getElementById('mobileZoomDisplay');\n        if (zd && mzd) mzd.textContent = zd.textContent;\n      });\n    }\n\n    connectMobileItem('mobileBtnZoomFit', () => {\n      const btn = document.getElementById('btnZoomFit');\n      if (btn) btn.click();\n    });\n\n    connectMobileItem('mobileBtnZoomReset', () => {\n      const btn = document.getElementById('btnZoomReset');\n      if (btn) btn.click();\n    });\n\n    connectMobileItem('mobileBtnToggleAutoZoom', () => {\n      this.toggleAutoZoom();\n    });\n\n    connectMobileItem('mobileBtnExportJson', () => {\n      const btn = document.getElementById('btnExportJson');\n      if (btn) btn.click();\n    });\n\n    connectMobileItem('mobileBtnExportPng', () => {\n      const btn = document.getElementById('btnExportPng');\n      if (btn) btn.click();\n    });\n\n    connectMobileItem('mobileBtnExportPdf', () => {\n      const btn = document.getElementById('btnExportPdf');\n      if (btn) btn.click();\n    });\n\n    connectMobileItem('mobileBtnImportJson', () => {\n      const fi = document.getElementById('fileInput');\n      if (fi) fi.click();\n    });\n\n    // Left Pane Tabs\n    const tabTreeBtn = document.getElementById('tabTreeBtn');\n    const tabLibraryBtn = document.getElementById('tabLibraryBtn');\n    const treeTab = document.getElementById('treeTab');\n    const libraryTab = document.getElementById('libraryTab');\n\n    if (tabTreeBtn && tabLibraryBtn) {\n      tabTreeBtn.addEventListener('click', () => {\n        tabTreeBtn.classList.add('active');\n        tabLibraryBtn.classList.remove('active');\n        treeTab.classList.add('active');\n        libraryTab.classList.remove('active');\n      });\n      tabLibraryBtn.addEventListener('click', () => {\n        tabLibraryBtn.classList.add('active');\n        tabTreeBtn.classList.remove('active');\n        libraryTab.classList.add('active');\n        treeTab.classList.remove('active');\n      });\n    }\n\n    // Modal: User Settings (Custom Names & Colors)\n    const btnOpenSettings = document.getElementById('btnOpenSettings');\n    if (btnOpenSettings) {\n      btnOpenSettings.addEventListener('click', () => {\n        const userProfileDropdown = document.getElementById('userProfileDropdown');\n        if (userProfileDropdown) userProfileDropdown.classList.remove('show');\n        this.openSettingsModal();\n      });\n    }\n\n    // Modal: Share & Permissions\n    const btnOpenShare = document.getElementById('btnOpenShare');\n    if (btnOpenShare) {\n      btnOpenShare.addEventListener('click', () => {\n        this.openShareModal();\n      });\n    }\n    this.initShareAutocomplete();\n    this.ensureDirectoryLoaded();\n\n    // User Profile Dropdown & Sign In / Out\n    const btnUserProfile = document.getElementById('btnUserProfile');\n    const userProfileDropdown = document.getElementById('userProfileDropdown');\n    if (btnUserProfile && userProfileDropdown) {\n      btnUserProfile.addEventListener('click', (e) => {\n        e.stopPropagation();\n        userProfileDropdown.classList.toggle('show');\n      });\n      window.addEventListener('click', (e) => {\n        if (!e.target.closest('.user-profile-wrapper')) {\n          userProfileDropdown.classList.remove('show');\n        }\n      });\n    }\n\n    // User Profile & Avatar Modal Listeners\n    const btnOpenMyProfile = document.getElementById('btnOpenMyProfile');\n    if (btnOpenMyProfile) {\n      btnOpenMyProfile.addEventListener('click', () => {\n        if (userProfileDropdown) userProfileDropdown.classList.remove('show');\n        this.openMyProfileModal();\n      });\n    }\n\n    const myProfileForm = document.getElementById('myProfileForm');\n    if (myProfileForm) {\n      myProfileForm.addEventListener('submit', (e) => this.saveMyProfile(e));\n    }\n\n    const myProfileAvatarFileInput = document.getElementById('myProfileAvatarFileInput');\n    if (myProfileAvatarFileInput) {\n      myProfileAvatarFileInput.addEventListener('change', async (e) => {\n        const file = e.target.files && e.target.files[0];\n        if (!file) return;\n        try {\n          const compressed = await this.compressImageFile(file, 256, 256, 0.85);\n          if (compressed) {\n            this.profileEditAvatarValue = compressed;\n            this.renderMyProfileAvatarPreview();\n          }\n        } catch (err) {\n          console.error('Error compressing profile image:', err);\n        }\n      });\n    }\n\n    const btnMyProfileClearAvatar = document.getElementById('btnMyProfileClearAvatar');\n    if (btnMyProfileClearAvatar) {\n      btnMyProfileClearAvatar.addEventListener('click', () => {\n        this.profileEditAvatarValue = null;\n        this.renderMyProfileAvatarPreview();\n        document.querySelectorAll('#myProfilePresetIcons .avatar-preset-chip').forEach(b => b.classList.remove('active'));\n      });\n    }\n\n    // Enterprise Admin Portal Triggers & Event Handlers\n    const btnOpenAdminPortal = document.getElementById('btnOpenAdminPortal');\n    if (btnOpenAdminPortal) {\n      btnOpenAdminPortal.addEventListener('click', () => {\n        if (userProfileDropdown) userProfileDropdown.classList.remove('show');\n        this.openAdminPortal();\n      });\n    }\n\n    const btnHeaderAdminPortal = document.getElementById('btnHeaderAdminPortal');\n    if (btnHeaderAdminPortal) {\n      btnHeaderAdminPortal.addEventListener('click', () => {\n        this.openAdminPortal();\n      });\n    }\n\n    const btnAdminCloseTab = document.getElementById('btnAdminCloseTab');\n    if (btnAdminCloseTab) {\n      btnAdminCloseTab.addEventListener('click', () => {\n        this.closeDiagram('admin');\n      });\n    }\n\n    const btnAdminCloseTabFooter = document.getElementById('btnAdminCloseTabFooter');\n    if (btnAdminCloseTabFooter) {\n      btnAdminCloseTabFooter.addEventListener('click', () => {\n        this.closeDiagram('admin');\n      });\n    }\n\n    // Admin CSV Import / Export Listeners\n    const btnAdminExportUsersCsv = document.getElementById('btnAdminExportUsersCsv');\n    if (btnAdminExportUsersCsv) {\n      btnAdminExportUsersCsv.addEventListener('click', () => this.exportAdminUsersCsv());\n    }\n\n    const btnAdminImportUsersCsv = document.getElementById('btnAdminImportUsersCsv');\n    if (btnAdminImportUsersCsv) {\n      btnAdminImportUsersCsv.addEventListener('click', () => this.openAdminImportUsersModal());\n    }\n\n    const btnDownloadCsvTemplate = document.getElementById('btnDownloadCsvTemplate');\n    if (btnDownloadCsvTemplate) {\n      btnDownloadCsvTemplate.addEventListener('click', () => this.downloadCsvTemplate());\n    }\n\n    const adminImportCsvFileInput = document.getElementById('adminImportCsvFileInput');\n    if (adminImportCsvFileInput) {\n      adminImportCsvFileInput.addEventListener('change', (e) => {\n        const file = e.target.files && e.target.files[0];\n        if (!file) return;\n        const reader = new FileReader();\n        reader.onload = (loadEvt) => {\n          const textArea = document.getElementById('adminImportCsvTextArea');\n          if (textArea) textArea.value = loadEvt.target.result;\n        };\n        reader.readAsText(file);\n      });\n    }\n\n    const btnSubmitAdminImportCsv = document.getElementById('btnSubmitAdminImportCsv');\n    if (btnSubmitAdminImportCsv) {\n      btnSubmitAdminImportCsv.addEventListener('click', () => this.submitAdminImportCsv());\n    }\n\n    // Admin User Profile Edit Listeners\n    const adminEditUserForm = document.getElementById('adminEditUserForm');\n    if (adminEditUserForm) {\n      adminEditUserForm.addEventListener('submit', (e) => this.saveAdminEditUser(e));\n    }\n\n    const btnAdminEditDeleteUser = document.getElementById('btnAdminEditDeleteUser');\n    if (btnAdminEditDeleteUser) {\n      btnAdminEditDeleteUser.addEventListener('click', () => this.deleteAdminUser());\n    }\n\n    const adminEditAvatarFileInput = document.getElementById('adminEditAvatarFileInput');\n    if (adminEditAvatarFileInput) {\n      adminEditAvatarFileInput.addEventListener('change', (e) => {\n        const file = e.target.files && e.target.files[0];\n        if (!file) return;\n        const reader = new FileReader();\n        reader.onload = (loadEvt) => {\n          this.adminEditAvatarValue = loadEvt.target.result;\n          this.renderAdminEditAvatarPreview();\n        };\n        reader.readAsDataURL(file);\n      });\n    }\n\n    const btnAdminEditClearAvatar = document.getElementById('btnAdminEditClearAvatar');\n    if (btnAdminEditClearAvatar) {\n      btnAdminEditClearAvatar.addEventListener('click', () => {\n        this.adminEditAvatarValue = null;\n        this.renderAdminEditAvatarPreview();\n        document.querySelectorAll('#adminEditPresetIcons .avatar-preset-chip').forEach(b => b.classList.remove('active'));\n      });\n    }\n\n    const btnAdminEditTriggerResetPw = document.getElementById('btnAdminEditTriggerResetPw');\n    if (btnAdminEditTriggerResetPw) {\n      btnAdminEditTriggerResetPw.addEventListener('click', () => {\n        if (!this.adminEditingTargetUser) return;\n        const modal = document.getElementById('modalAdminEditUser');\n        if (modal) modal.classList.remove('show');\n        this.openAdminResetPasswordModal(this.adminEditingTargetUser.id, this.adminEditingTargetUser.email);\n      });\n    }\n\n    const tabAdminUsersBtn = document.getElementById('tabAdminUsersBtn');\n    if (tabAdminUsersBtn) {\n      tabAdminUsersBtn.addEventListener('click', () => this.switchAdminTab('users'));\n    }\n\n    const tabAdminConfigBtn = document.getElementById('tabAdminConfigBtn');\n    if (tabAdminConfigBtn) {\n      tabAdminConfigBtn.addEventListener('click', () => this.switchAdminTab('config'));\n    }\n\n    const tabAdminSuggestionsBtn = document.getElementById('tabAdminSuggestionsBtn');\n    if (tabAdminSuggestionsBtn) {\n      tabAdminSuggestionsBtn.addEventListener('click', () => this.switchAdminTab('suggestions'));\n    }\n\n    const tabAdminLicenseBtn = document.getElementById('tabAdminLicenseBtn');\n    if (tabAdminLicenseBtn) {\n      tabAdminLicenseBtn.addEventListener('click', () => this.switchAdminTab('license'));\n    }\n\n    const btnBannerActivateLicense = document.getElementById('btnBannerActivateLicense');\n    if (btnBannerActivateLicense) {\n      btnBannerActivateLicense.addEventListener('click', () => {\n        if (this.currentUser && this.currentUser.is_admin) {\n          this.openAdminTab();\n          this.switchAdminTab('license');\n        } else {\n          this.showNotification('Please ask your Instance Administrator to upload or paste the .btlic license key in the Admin Console.', 'warning');\n        }\n      });\n    }\n\n    const adminLicenseFileInput = document.getElementById('adminLicenseFileInput');\n    if (adminLicenseFileInput) {\n      adminLicenseFileInput.addEventListener('change', (e) => {\n        const file = e.target.files && e.target.files[0];\n        if (!file) return;\n        const reader = new FileReader();\n        reader.onload = (evt) => {\n          const keyInput = document.getElementById('adminLicenseKeyInput');\n          if (keyInput) keyInput.value = evt.target.result || '';\n        };\n        reader.readAsText(file);\n      });\n    }\n\n    const btnAdminActivateLicense = document.getElementById('btnAdminActivateLicense');\n    if (btnAdminActivateLicense) {\n      btnAdminActivateLicense.addEventListener('click', () => this.activateAdminLicense());\n    }\n\n    const btnAdminDeactivateLicense = document.getElementById('btnAdminDeactivateLicense');\n    if (btnAdminDeactivateLicense) {\n      btnAdminDeactivateLicense.addEventListener('click', () => this.deactivateAdminLicense());\n    }\n\n    const btnAdminSaveUpdateSettings = document.getElementById('btnAdminSaveUpdateSettings');\n    if (btnAdminSaveUpdateSettings) {\n      btnAdminSaveUpdateSettings.addEventListener('click', () => this.saveAdminUpdateSettings());\n    }\n\n    const adminAutoUpdateToggle = document.getElementById('adminAutoUpdateToggle');\n    if (adminAutoUpdateToggle) {\n      adminAutoUpdateToggle.addEventListener('change', () => this.saveAdminUpdateSettings());\n    }\n\n    const btnAdminApplyOnlineUpdate = document.getElementById('btnAdminApplyOnlineUpdate');\n    if (btnAdminApplyOnlineUpdate) {\n      btnAdminApplyOnlineUpdate.addEventListener('click', () => this.applyAdminOnlineUpdate());\n    }\n\n    const btnAdminImportOfflineUpdate = document.getElementById('btnAdminImportOfflineUpdate');\n    if (btnAdminImportOfflineUpdate) {\n      btnAdminImportOfflineUpdate.addEventListener('click', () => this.importAdminOfflineUpdate());\n    }\n\n    const btnAdminRollbackUpdate = document.getElementById('btnAdminRollbackUpdate');\n    if (btnAdminRollbackUpdate) {\n      btnAdminRollbackUpdate.addEventListener('click', () => this.rollbackAdminUpdate());\n    }\n\n    const btnAdminResetSuggestions = document.getElementById('btnAdminResetSuggestions');\n    if (btnAdminResetSuggestions) {\n      btnAdminResetSuggestions.addEventListener('click', () => this.resetAdminSuggestions());\n    }\n\n    const btnAdminSaveSuggestions = document.getElementById('btnAdminSaveSuggestions');\n    if (btnAdminSaveSuggestions) {\n      btnAdminSaveSuggestions.addEventListener('click', () => this.saveAdminSuggestions());\n    }\n\n    const adminUserSearchInput = document.getElementById('adminUserSearchInput');\n    if (adminUserSearchInput) {\n      adminUserSearchInput.addEventListener('input', () => this.renderAdminUsersTable());\n    }\n\n    const btnAdminRefreshUsers = document.getElementById('btnAdminRefreshUsers');\n    if (btnAdminRefreshUsers) {\n      btnAdminRefreshUsers.addEventListener('click', () => this.loadAdminUsers());\n    }\n\n    const cfgRegistrationMode = document.getElementById('cfgRegistrationMode');\n    if (cfgRegistrationMode) {\n      cfgRegistrationMode.addEventListener('change', (e) => {\n        const domainGrp = document.getElementById('cfgDomainGroup');\n        if (domainGrp) {\n          domainGrp.style.display = e.target.value === 'restricted_domain' ? 'block' : 'none';\n        }\n      });\n    }\n\n    const cfgSmtpPreset = document.getElementById('cfgSmtpPreset');\n    if (cfgSmtpPreset) {\n      cfgSmtpPreset.addEventListener('change', (e) => this.applySmtpPreset(e.target.value));\n    }\n\n    const btnAdminTestEmail = document.getElementById('btnAdminTestEmail');\n    if (btnAdminTestEmail) {\n      btnAdminTestEmail.addEventListener('click', () => this.sendAdminTestEmail());\n    }\n\n    const adminConfigForm = document.getElementById('adminConfigForm');\n    if (adminConfigForm) {\n      adminConfigForm.addEventListener('submit', (e) => this.saveAdminConfig(e));\n    }\n\n    const btnAdminReloadConfig = document.getElementById('btnAdminReloadConfig');\n    if (btnAdminReloadConfig) {\n      btnAdminReloadConfig.addEventListener('click', () => this.loadAdminConfig());\n    }\n\n    const adminResetPasswordForm = document.getElementById('adminResetPasswordForm');\n    if (adminResetPasswordForm) {\n      adminResetPasswordForm.addEventListener('submit', (e) => this.submitAdminResetPassword(e));\n    }\n\n    const btnSignOut = document.getElementById('btnSignOut');\n    if (btnSignOut) {\n      btnSignOut.addEventListener('click', async () => {\n        if (userProfileDropdown) userProfileDropdown.classList.remove('show');\n        try {\n          await this.apiFetch('/api/auth/logout', { method: 'POST' });\n        } catch (e) {\n          console.error(e);\n        }\n        localStorage.removeItem('bowtie_auth_token');\n        localStorage.removeItem('bowtie_open_diagram_ids');\n        this.authToken = null;\n        this.currentUser = null;\n        this.workspace = { diagrams: [], nodes: [], folders: [] };\n        this.openDiagramIds = [];\n        this.currentDiagram = null;\n        if (this.nodesLayerEl) this.nodesLayerEl.innerHTML = '';\n        if (this.connectionsGroupEl) this.connectionsGroupEl.innerHTML = '';\n        if (this.realtimeSource) {\n          this.realtimeSource.close();\n          this.realtimeSource = null;\n        }\n        const formSignup = document.getElementById('wallSignupForm');\n        if (formSignup) formSignup.reset();\n        const formLogin = document.getElementById('wallLoginForm');\n        if (formLogin) {\n          const pass = document.getElementById('wallLoginPassword');\n          if (pass) pass.value = '';\n        }\n        this.showAuthWall('You have signed out. Please sign in to access protected assessments.');\n      });\n    }\n\n    // Modal: New Diagram\n    const btnAddDiagram = document.getElementById('btnAddDiagram');\n    const chkAddToCurrentDiagram = document.getElementById('chkAddToCurrentDiagram');\n    const groupNewDiagTitle = document.getElementById('groupNewDiagTitle');\n    const groupNewDiagDesc = document.getElementById('groupNewDiagDesc');\n    const modalNewDiagramHeading = document.getElementById('modalNewDiagramHeading');\n    const btnSubmitNewDiagram = document.getElementById('btnSubmitNewDiagram');\n\n    const updateNewDiagramModalState = () => {\n      const isAddCluster = chkAddToCurrentDiagram && chkAddToCurrentDiagram.checked;\n      if (groupNewDiagTitle) groupNewDiagTitle.style.display = isAddCluster ? 'none' : 'block';\n      if (groupNewDiagDesc) groupNewDiagDesc.style.display = isAddCluster ? 'none' : 'block';\n      if (modalNewDiagramHeading) modalNewDiagramHeading.textContent = isAddCluster ? 'Add Hazard & Top Event to Diagram' : 'Create New Bowtie Diagram';\n      if (btnSubmitNewDiagram) btnSubmitNewDiagram.textContent = isAddCluster ? 'Add to Current Diagram' : 'Create Diagram on Canvas';\n    };\n\n    if (chkAddToCurrentDiagram) {\n      chkAddToCurrentDiagram.addEventListener('change', updateNewDiagramModalState);\n    }\n\n    if (btnAddDiagram) {\n      btnAddDiagram.addEventListener('click', () => {\n        if (!this.isInstanceLicensedForEdit()) {\n          this.showUnlicensedPrompt();\n          return;\n        }\n        const form = document.getElementById('newDiagramForm');\n        if (form) form.reset();\n        if (chkAddToCurrentDiagram) chkAddToCurrentDiagram.checked = false;\n        updateNewDiagramModalState();\n        document.getElementById('modalNewDiagram').classList.add('show');\n      });\n    }\n\n    const newDiagForm = document.getElementById('newDiagramForm');\n    if (newDiagForm) {\n      newDiagForm.addEventListener('submit', (e) => {\n        e.preventDefault();\n        const isAddCluster = chkAddToCurrentDiagram && chkAddToCurrentDiagram.checked;\n        const hazard = (document.getElementById('newDiagHazard')?.value || '').trim();\n        const topEvent = (document.getElementById('newDiagTopEvent')?.value || '').trim();\n\n        if (isAddCluster) {\n          if (!this.currentDiagram) {\n            this.showNotification('Please open or create an assessment first.', 'warning');\n            return;\n          }\n          this.addHazardAndTopEventToCurrentDiagram(hazard, topEvent);\n          document.getElementById('modalNewDiagram').classList.remove('show');\n          newDiagForm.reset();\n          updateNewDiagramModalState();\n          return;\n        }\n\n        const titleInput = document.getElementById('newDiagTitle');\n        const title = (titleInput ? titleInput.value : '').trim();\n        if (!title) {\n          this.showNotification('Please enter a diagram title', 'warning');\n          if (titleInput) {\n            titleInput.classList.add('input-error');\n            titleInput.focus();\n          }\n          return;\n        }\n\n        const desc = (document.getElementById('newDiagDesc')?.value || '').trim();\n\n        this.createNewDiagram(title, desc, hazard, topEvent);\n        if (titleInput) titleInput.classList.remove('input-error');\n        document.getElementById('modalNewDiagram').classList.remove('show');\n        newDiagForm.reset();\n        updateNewDiagramModalState();\n      });\n    }\n\n    // Modal: New Master Global Element\n    const btnNewGlobalElement = document.getElementById('btnNewGlobalElement');\n    if (btnNewGlobalElement) {\n      btnNewGlobalElement.addEventListener('click', () => {\n        if (!this.isInstanceLicensedForEdit()) {\n          this.showUnlicensedPrompt();\n          return;\n        }\n        document.getElementById('modalNewGlobalElem').classList.add('show');\n      });\n    }\n\n    const newGlobalElemForm = document.getElementById('newGlobalElemForm');\n    if (newGlobalElemForm) {\n      newGlobalElemForm.addEventListener('submit', async (e) => {\n        e.preventDefault();\n        const nameInput = document.getElementById('geName');\n        const name = (nameInput ? nameInput.value : '').trim();\n        if (!name) {\n          this.showNotification('Please enter an element name', 'warning');\n          if (nameInput) {\n            nameInput.classList.add('input-error');\n            nameInput.focus();\n          }\n          return;\n        }\n\n        const type = document.getElementById('geType').value;\n        const role = document.getElementById('geRole').value.trim();\n        const effectiveness = document.getElementById('geEffectiveness').value;\n        const desc = document.getElementById('geDesc').value.trim();\n\n        const newGe = {\n          id: `ge-${Date.now().toString(36)}`,\n          name,\n          node_type: type,\n          description: desc,\n          responsible_role: role,\n          effectiveness,\n          criticality: 'Safety Critical Element',\n          tags: ['Master'],\n          created_by: this.currentUser ? this.currentUser.email : 'user',\n          created_at: new Date().toISOString(),\n          updated_at: new Date().toISOString(),\n        };\n\n        if (!this.workspace.global_elements) this.workspace.global_elements = [];\n        this.workspace.global_elements.push(newGe);\n\n        await this.saveWorkspaceToBackend(`Created reusable master element '${name}'`);\n        this.tree.renderLibrary();\n        if (nameInput) nameInput.classList.remove('input-error');\n        document.getElementById('modalNewGlobalElem').classList.remove('show');\n        newGlobalElemForm.reset();\n        this.showNotification(`Created master element '${name}'!`, 'success');\n      });\n    }\n  }\n\n  initToolbarButtons() {\n    // Brand Logo navigation to Document View\n    const brandLogo = document.getElementById('navBrandLogo') || document.querySelector('.brand-logo');\n    if (brandLogo && !brandLogo._boundDocView) {\n      brandLogo._boundDocView = true;\n      brandLogo.addEventListener('click', () => this.switchToView('drive'));\n      brandLogo.addEventListener('keydown', (e) => {\n        if (e.key === 'Enter' || e.key === ' ') {\n          e.preventDefault();\n          this.switchToView('drive');\n        }\n      });\n    }\n\n    if (this._toolbarButtonsInitialized) return;\n    this._toolbarButtonsInitialized = true;\n\n    // View Switcher (Documents Drive vs Canvas)\n    const btnViewDrive = document.getElementById('btnViewDrive');\n    const btnViewCanvas = document.getElementById('btnViewCanvas');\n    if (btnViewDrive) btnViewDrive.addEventListener('click', () => this.switchToView('drive'));\n    if (btnViewCanvas) btnViewCanvas.addEventListener('click', () => this.switchToView('canvas'));\n\n    // Zoom Controls\n    document.getElementById('btnZoomIn').addEventListener('click', () => this.canvas.zoomIn());\n    document.getElementById('btnZoomOut').addEventListener('click', () => this.canvas.zoomOut());\n    document.getElementById('btnZoomReset').addEventListener('click', () => this.canvas.zoomReset());\n    document.getElementById('btnZoomFit').addEventListener('click', () => this.canvas.zoomFit(true));\n\n    // Canvas Top Floating Tools\n    document.getElementById('toolAddThreat').addEventListener('click', () => this.addThreatToCurrentDiagram());\n    document.getElementById('toolAddOutcome').addEventListener('click', () => this.addOutcomeToCurrentDiagram());\n    document.getElementById('toolAddBarrier').addEventListener('click', () => this.addBarrierToCurrentSelection());\n    document.getElementById('toolAddEscalation').addEventListener('click', () => {\n      let active = this.getNodeById(this.canvas.activeNodeId);\n      if (active && (active.node_type === 'threat_barrier' || active.node_type === 'outcome_barrier')) {\n        this.handleAddChildNode(active);\n        return;\n      }\n      if (active && active.node_type === 'escalation_factor') {\n        // Add another EF under the same parent barrier\n        const parent = this.getNodeById(active.parent_id);\n        if (parent) {\n          this.handleAddChildNode(parent);\n          return;\n        }\n      }\n      if (active && active.node_type === 'mitigation') {\n        let parent = this.getNodeById(active.parent_id);\n        while (parent && (parent.node_type === 'escalation_factor' || parent.node_type === 'mitigation')) {\n          parent = this.getNodeById(parent.parent_id);\n        }\n        if (parent && (parent.node_type === 'threat_barrier' || parent.node_type === 'outcome_barrier')) {\n          this.handleAddChildNode(parent);\n          return;\n        }\n      }\n      // If no barrier is currently selected, find the first available barrier in the diagram\n      const diag = this.getActiveDiagram();\n      if (diag) {\n        const diagNodes = this.workspace.nodes.filter(n => n.diagram_id === diag.id);\n        const firstBarrier = diagNodes.find(n => n.node_type === 'threat_barrier' || n.node_type === 'outcome_barrier');\n        if (firstBarrier) {\n          this.handleAddChildNode(firstBarrier);\n          this.showNotification(`Added Escalation Factor to ${firstBarrier.title}`, 'success');\n          return;\n        }\n      }\n      this.showNotification('Please add a Barrier first before adding an Escalation Factor.', 'info');\n    });\n\n    const toolAddMit = document.getElementById('toolAddMitigation');\n    if (toolAddMit) {\n      toolAddMit.addEventListener('click', () => this.addMitigationToCurrentSelection());\n    }\n\n    const btnAutoZoom = document.getElementById('btnToggleAutoZoom');\n    if (btnAutoZoom) {\n      btnAutoZoom.addEventListener('click', () => this.toggleAutoZoom());\n    }\n\n    const hudAutoZoom = document.getElementById('hudAutoZoomToggle');\n    if (hudAutoZoom) {\n      hudAutoZoom.addEventListener('click', (e) => {\n        e.stopPropagation();\n        this.toggleAutoZoom();\n      });\n    }\n\n    // Line Up / Auto-Balance Layout Handlers\n    const handleLineUp = () => {\n      const diag = this.getActiveDiagram();\n      if (diag) {\n        this.pushUndoState('Line up elements', diag.id);\n        this.normalizeNodeDimensions();\n        this.fixInvertedOutcomeBarriers();\n        this.layout.balanceDiagram(diag.id);\n        this.showNotification('Diagram elements lined up!', 'success');\n        this.scheduleAutoSave('Lined up diagram elements');\n      }\n    };\n\n    const btnAutoBalance = document.getElementById('btnAutoBalance');\n    if (btnAutoBalance) {\n      btnAutoBalance.addEventListener('click', handleLineUp);\n    }\n\n    const btnToggleAutoAlign = document.getElementById('btnToggleAutoAlign');\n    if (btnToggleAutoAlign) {\n      btnToggleAutoAlign.addEventListener('click', () => {\n        this.toggleAutoAlign();\n      });\n    }\n    this.updateAutoAlignUI();\n\n    const btnOrderBarriers = document.getElementById('btnOrderBarriersTime');\n    if (btnOrderBarriers) {\n      btnOrderBarriers.addEventListener('click', () => {\n        this.orderBarriersByTime();\n      });\n    }\n\n    // In-Session Instant Undo & Redo Controls\n    // In-Session Instant Undo & Redo Controls (Top Header Controls)\n    const btnHeaderUndo = document.getElementById('btnHeaderUndo');\n    const btnUndo = document.getElementById('btnUndo');\n    if (btnHeaderUndo) {\n      btnHeaderUndo.addEventListener('click', () => this.undo());\n    }\n    if (btnUndo) {\n      btnUndo.addEventListener('click', () => this.undo());\n    }\n\n    const btnHeaderRedo = document.getElementById('btnHeaderRedo');\n    const btnRedo = document.getElementById('btnRedo');\n    if (btnHeaderRedo) {\n      btnHeaderRedo.addEventListener('click', () => this.redo());\n    }\n    if (btnRedo) {\n      btnRedo.addEventListener('click', () => this.redo());\n    }\n\n    // Diagram Change History Dropdown Controls (Main Navigation Bar)\n    const btnOpenHistory = document.getElementById('btnOpenHistory');\n    const btnDiagramHistory = document.getElementById('btnDiagramHistory');\n    const diagramHistoryDropdown = document.getElementById('diagramHistoryDropdown');\n    const diagramHistoryBackdrop = document.getElementById('diagramHistoryBackdrop');\n    const btnCloseHistoryDropdown = document.getElementById('btnCloseHistoryDropdown');\n    const btnAdminOpenAuditLog = document.getElementById('btnAdminOpenAuditLog');\n\n    if (btnOpenHistory) {\n      btnOpenHistory.addEventListener('click', (e) => {\n        e.stopPropagation();\n        this.toggleDiagramHistory();\n      });\n    }\n\n    if (btnDiagramHistory) {\n      btnDiagramHistory.addEventListener('click', (e) => {\n        e.stopPropagation();\n        this.toggleDiagramHistory();\n      });\n    }\n\n    if (diagramHistoryBackdrop) {\n      diagramHistoryBackdrop.addEventListener('click', (e) => {\n        e.stopPropagation();\n        this.closeDiagramHistory();\n      });\n    }\n\n    if (btnCloseHistoryDropdown) {\n      btnCloseHistoryDropdown.addEventListener('click', (e) => {\n        e.stopPropagation();\n        this.closeDiagramHistory();\n      });\n    }\n\n    if (btnAdminOpenAuditLog) {\n      btnAdminOpenAuditLog.addEventListener('click', (e) => {\n        e.stopPropagation();\n        this.openHistoryModal();\n      });\n    }\n\n    window.addEventListener('click', (e) => {\n      if (diagramHistoryDropdown && diagramHistoryDropdown.style.display !== 'none') {\n        const mobileBtn = document.getElementById('mobileBtnHistory');\n        const isTrigger = (btnOpenHistory && btnOpenHistory.contains(e.target)) ||\n                          (btnDiagramHistory && btnDiagramHistory.contains(e.target)) ||\n                          (mobileBtn && mobileBtn.contains(e.target));\n        if (!diagramHistoryDropdown.contains(e.target) && !isTrigger) {\n          this.closeDiagramHistory();\n        }\n      }\n    });\n\n    // Keyboard Shortcuts (Ctrl+Z / Cmd+Z for Undo, Ctrl+Y / Cmd+Shift+Z for Redo)\n    window.addEventListener('keydown', (e) => {\n      const tag = e.target && e.target.tagName ? e.target.tagName.toLowerCase() : '';\n      if (tag === 'input' || tag === 'textarea' || (e.target && e.target.isContentEditable)) {\n        return;\n      }\n\n      const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;\n      const cmdOrCtrl = isMac ? e.metaKey : e.ctrlKey;\n\n      if (cmdOrCtrl && !e.shiftKey && e.key.toLowerCase() === 'z') {\n        e.preventDefault();\n        this.undo();\n      } else if ((cmdOrCtrl && e.shiftKey && e.key.toLowerCase() === 'z') || (cmdOrCtrl && e.key.toLowerCase() === 'y')) {\n        e.preventDefault();\n        this.redo();\n      }\n    });\n\n    // Cost vs Protection Financial Analysis Controls\n    const btnOpenCostAnalysis = document.getElementById('btnOpenCostAnalysis');\n    if (btnOpenCostAnalysis) {\n      btnOpenCostAnalysis.addEventListener('click', () => this.openCostAnalysisModal());\n    }\n\n    const btnExportCostCsv = document.getElementById('btnExportCostCsv');\n    if (btnExportCostCsv) {\n      btnExportCostCsv.addEventListener('click', () => this.exportCostCsv());\n    }\n\n    this.updateUndoRedoUI();\n    this.updateAutoZoomUI();\n\n    // Export Dropdown\n    const exportBtn = document.getElementById('btnExportMenu');\n    const exportDropdown = document.getElementById('exportDropdown');\n    exportBtn.addEventListener('click', (e) => {\n      e.stopPropagation();\n      exportDropdown.classList.toggle('show');\n    });\n\n    window.addEventListener('click', () => {\n      exportDropdown.classList.remove('show');\n    });\n\n    document.getElementById('btnExportJson').addEventListener('click', () => this.exporter.downloadSaveFile());\n    document.getElementById('btnExportPng').addEventListener('click', () => this.exporter.exportPng());\n    document.getElementById('btnExportPdf').addEventListener('click', () => this.exporter.exportPdf());\n\n    const fileInput = document.getElementById('fileInput');\n    document.getElementById('btnImportJson').addEventListener('click', () => fileInput.click());\n    fileInput.addEventListener('change', (e) => {\n      if (e.target.files.length > 0) {\n        this.exporter.handleImportFile(e.target.files[0]);\n      }\n    });\n  }\n\n  // ==========================================================================\n  // IN-SESSION UNDO / REDO ENGINE (ISOLATED PER DIAGRAM)\n  // ==========================================================================\n  getUndoStack(diagId = null) {\n    const id = diagId || (this.currentDiagram ? this.currentDiagram.id : null);\n    if (!id) return [];\n    if (!this.undoStacks[id]) {\n      this.undoStacks[id] = [];\n    }\n    return this.undoStacks[id];\n  }\n\n  getRedoStack(diagId = null) {\n    const id = diagId || (this.currentDiagram ? this.currentDiagram.id : null);\n    if (!id) return [];\n    if (!this.redoStacks[id]) {\n      this.redoStacks[id] = [];\n    }\n    return this.redoStacks[id];\n  }\n\n  captureWorkspaceSnapshot(targetDiagId = null) {\n    if (!this.workspace) return null;\n    const diagId = targetDiagId || (this.currentDiagram ? this.currentDiagram.id : null);\n    if (!diagId) return null;\n\n    const diag = this.workspace.diagrams ? this.workspace.diagrams.find(d => d.id === diagId) : null;\n    const diagNodes = (this.workspace.nodes || []).filter(n => n.diagram_id === diagId);\n\n    return {\n      diagramId: diagId,\n      nodes: JSON.parse(JSON.stringify(diagNodes)),\n      diagram: diag ? JSON.parse(JSON.stringify(diag)) : null,\n      activeNodeId: (this.currentDiagram && this.currentDiagram.id === diagId && this.canvas) ? this.canvas.activeNodeId : null\n    };\n  }\n\n  pushUndoState(description = 'Edit', targetDiagId = null) {\n    if (this.isPerformingUndoRedo) return;\n    const diagId = targetDiagId || (this.currentDiagram ? this.currentDiagram.id : null);\n    if (!diagId) return;\n    if (!this.canEditCurrentDiagram()) return;\n\n    const snapshot = this.captureWorkspaceSnapshot(diagId);\n    if (!snapshot) return;\n\n    const stack = this.getUndoStack(diagId);\n    stack.push({ snapshot, description, timestamp: Date.now() });\n    if (stack.length > this.maxUndoStack) {\n      stack.shift();\n    }\n    if (this.redoStacks[diagId]) {\n      this.redoStacks[diagId] = [];\n    }\n    this.updateUndoRedoUI();\n  }\n\n  pushSnapshotToUndo(preMutationSnapshot, description = 'Edit') {\n    if (this.isPerformingUndoRedo || !preMutationSnapshot) return;\n    if (!this.canEditCurrentDiagram()) return;\n    let snapshot = preMutationSnapshot;\n    let diagId = snapshot.diagramId || (this.currentDiagram ? this.currentDiagram.id : null);\n\n    if (Array.isArray(preMutationSnapshot)) {\n      if (!diagId) return;\n      const diag = this.workspace && this.workspace.diagrams ? this.workspace.diagrams.find(d => d.id === diagId) : null;\n      snapshot = {\n        diagramId: diagId,\n        nodes: JSON.parse(JSON.stringify(preMutationSnapshot.filter(n => n.diagram_id === diagId))),\n        diagram: diag ? JSON.parse(JSON.stringify(diag)) : null,\n        activeNodeId: this.canvas ? this.canvas.activeNodeId : null\n      };\n    } else if (!snapshot.diagramId && snapshot.currentDiagramId) {\n      diagId = snapshot.currentDiagramId;\n      snapshot.diagramId = diagId;\n      if (snapshot.nodes && (!snapshot.nodes.length || snapshot.nodes[0].diagram_id !== undefined)) {\n        snapshot.nodes = snapshot.nodes.filter(n => n.diagram_id === diagId);\n      }\n    }\n\n    if (!diagId) return;\n\n    const stack = this.getUndoStack(diagId);\n    stack.push({ snapshot, description, timestamp: Date.now() });\n    if (stack.length > this.maxUndoStack) {\n      stack.shift();\n    }\n    if (this.redoStacks[diagId]) {\n      this.redoStacks[diagId] = [];\n    }\n    this.updateUndoRedoUI();\n  }\n\n  undo(silent = false) {\n    const diagId = this.currentDiagram ? this.currentDiagram.id : null;\n    if (!diagId) {\n      if (!silent) this.showNotification('No active diagram to undo.', 'info');\n      return;\n    }\n    const undoStack = this.getUndoStack(diagId);\n    if (undoStack.length === 0) {\n      if (!silent) this.showNotification('Nothing to undo.', 'info');\n      return;\n    }\n    this.isPerformingUndoRedo = true;\n    try {\n      const currentState = this.captureWorkspaceSnapshot(diagId);\n      const prevAction = undoStack.pop();\n      const redoStack = this.getRedoStack(diagId);\n      redoStack.push({ snapshot: currentState, description: prevAction.description, timestamp: Date.now() });\n\n      this.applySnapshot(prevAction.snapshot);\n      this.updateUndoRedoUI();\n      if (!silent) {\n        this.showNotification(`Undid: ${prevAction.description}`, 'info');\n      }\n      this.scheduleAutoSave(`Undid ${prevAction.description}`);\n    } finally {\n      this.isPerformingUndoRedo = false;\n    }\n  }\n\n  redo(silent = false) {\n    const diagId = this.currentDiagram ? this.currentDiagram.id : null;\n    if (!diagId) {\n      if (!silent) this.showNotification('No active diagram to redo.', 'info');\n      return;\n    }\n    const redoStack = this.getRedoStack(diagId);\n    if (redoStack.length === 0) {\n      if (!silent) this.showNotification('Nothing to redo.', 'info');\n      return;\n    }\n    this.isPerformingUndoRedo = true;\n    try {\n      const currentState = this.captureWorkspaceSnapshot(diagId);\n      const nextAction = redoStack.pop();\n      const undoStack = this.getUndoStack(diagId);\n      undoStack.push({ snapshot: currentState, description: nextAction.description, timestamp: Date.now() });\n\n      this.applySnapshot(nextAction.snapshot);\n      this.updateUndoRedoUI();\n      if (!silent) {\n        this.showNotification(`Redid: ${nextAction.description}`, 'info');\n      }\n      this.scheduleAutoSave(`Redid ${nextAction.description}`);\n    } finally {\n      this.isPerformingUndoRedo = false;\n    }\n  }\n\n  applySnapshot(snapshot) {\n    if (!snapshot || !this.workspace) return;\n    const diagId = snapshot.diagramId || snapshot.currentDiagramId;\n    if (!diagId) return;\n\n    // Retain all nodes belonging to other diagrams, replace this diagram's nodes\n    const otherNodes = (this.workspace.nodes || []).filter(n => n.diagram_id !== diagId);\n    const restoredNodes = snapshot.diagramId\n      ? JSON.parse(JSON.stringify(snapshot.nodes || []))\n      : JSON.parse(JSON.stringify((snapshot.nodes || []).filter(n => n.diagram_id === diagId)));\n\n    this.workspace.nodes = otherNodes.concat(restoredNodes);\n\n    if (snapshot.diagram && this.workspace.diagrams) {\n      const idx = this.workspace.diagrams.findIndex(d => d.id === diagId);\n      if (idx !== -1) {\n        this.workspace.diagrams[idx] = JSON.parse(JSON.stringify(snapshot.diagram));\n      }\n    }\n\n    if (this.currentDiagram && this.currentDiagram.id === diagId) {\n      const current = this.workspace.diagrams ? this.workspace.diagrams.find(d => d.id === diagId) : null;\n      if (current) {\n        this.currentDiagram = current;\n      }\n      const titleEl = document.getElementById('currentDiagramTitle');\n      if (titleEl && this.currentDiagram) {\n        titleEl.textContent = this.currentDiagram.title;\n      }\n    }\n\n    this.normalizeNodeDimensions();\n    this.fixInvertedOutcomeBarriers();\n\n    if (this.canvas) {\n      this.canvas.render();\n      if (snapshot.activeNodeId) {\n        this.canvas.setActiveNode(snapshot.activeNodeId);\n        if (this.inspector) {\n          const activeNode = this.getNodeById(snapshot.activeNodeId);\n          if (activeNode) {\n            this.inspector.inspect(activeNode.id);\n          } else {\n            this.inspector.inspect(null);\n          }\n        }\n      } else {\n        if (this.inspector) this.inspector.inspect(null);\n      }\n    }\n\n    if (this.tree) this.tree.render();\n    this.renderDiagramTabs();\n    this.initDiagramSelector();\n  }\n\n  updateUndoRedoUI() {\n    const btnHeaderUndo = document.getElementById('btnHeaderUndo');\n    const btnHeaderRedo = document.getElementById('btnHeaderRedo');\n    const btnUndo = document.getElementById('btnUndo');\n    const btnRedo = document.getElementById('btnRedo');\n    const badgeEl = document.getElementById('historyCountBadge');\n    const undoStack = this.getUndoStack();\n    const redoStack = this.getRedoStack();\n\n    const topUndo = undoStack[undoStack.length - 1];\n    const topRedo = redoStack[redoStack.length - 1];\n\n    if (btnHeaderUndo) {\n      btnHeaderUndo.disabled = undoStack.length === 0;\n      btnHeaderUndo.title = topUndo ? `Undo \"${topUndo.description}\" (Ctrl+Z / Cmd+Z)` : 'Undo (Ctrl+Z / Cmd+Z)';\n    }\n    if (btnHeaderRedo) {\n      btnHeaderRedo.disabled = redoStack.length === 0;\n      btnHeaderRedo.title = topRedo ? `Redo \"${topRedo.description}\" (Ctrl+Y / Cmd+Shift+Z)` : 'Redo (Ctrl+Y / Cmd+Shift+Z)';\n    }\n\n    if (btnUndo) {\n      btnUndo.disabled = undoStack.length === 0;\n      btnUndo.title = topUndo ? `Undo \"${topUndo.description}\" (Ctrl+Z / Cmd+Z)` : 'Undo (Ctrl+Z / Cmd+Z)';\n    }\n    if (btnRedo) {\n      btnRedo.disabled = redoStack.length === 0;\n      btnRedo.title = topRedo ? `Redo \"${topRedo.description}\" (Ctrl+Y / Cmd+Shift+Z)` : 'Redo (Ctrl+Y / Cmd+Shift+Z)';\n    }\n    if (badgeEl) {\n      badgeEl.textContent = undoStack.length;\n      badgeEl.style.display = undoStack.length > 0 ? 'inline-flex' : 'none';\n    }\n\n    const dropdown = document.getElementById('diagramHistoryDropdown');\n    if (dropdown && dropdown.style.display === 'flex') {\n      this.renderDiagramHistory();\n    }\n  }\n\n  toggleAutoZoom() {\n    if (!this.canvas) return;\n    this.canvas.autoZoomEnabled = !this.canvas.autoZoomEnabled;\n    try {\n      localStorage.setItem('bowtie_auto_zoom', this.canvas.autoZoomEnabled ? 'true' : 'false');\n    } catch (_) {}\n    this.updateAutoZoomUI();\n    if (this.canvas.autoZoomEnabled) {\n      this.showNotification('Auto Zoom enabled: zooms on active element, fits on deselect', 'info');\n      if (this.canvas.activeNodeId) {\n        this.canvas.zoomOnActiveElement(true);\n      } else {\n        this.canvas.zoomFit(true);\n      }\n    } else {\n      this.showNotification('Auto Zoom disabled', 'info');\n    }\n  }\n\n  updateAutoZoomUI() {\n    const isEnabled = this.canvas ? !!this.canvas.autoZoomEnabled : false;\n    const hudPill = document.getElementById('hudAutoZoomToggle');\n    if (hudPill) {\n      hudPill.textContent = isEnabled ? 'AUTO: ON' : 'AUTO: OFF';\n      hudPill.classList.toggle('active', isEnabled);\n    }\n    const btnNav = document.getElementById('btnToggleAutoZoom');\n    if (btnNav) {\n      btnNav.classList.toggle('active', isEnabled);\n      btnNav.title = isEnabled ? 'Auto Zoom: Enabled (Click to Disable)' : 'Auto Zoom: Disabled (Click to Enable)';\n    }\n    const mobileText = document.getElementById('mobileAutoZoomText');\n    if (mobileText) {\n      mobileText.textContent = isEnabled ? 'Auto Zoom: ON' : 'Auto Zoom: OFF';\n    }\n  }\n\n  toggleAutoAlign() {\n    this.autoAlignEnabled = !this.autoAlignEnabled;\n    try {\n      localStorage.setItem('bowtie_auto_align_enabled', this.autoAlignEnabled ? 'true' : 'false');\n    } catch (_) {}\n    this.updateAutoAlignUI();\n    this.showNotification(\n      this.autoAlignEnabled ? 'Auto Align enabled: elements will align on any layout change' : 'Auto Align disabled',\n      'info'\n    );\n    if (this.autoAlignEnabled) {\n      this.triggerLineUpElements(true);\n    }\n  }\n\n  updateAutoAlignUI() {\n    const isEnabled = !!this.autoAlignEnabled;\n    const btnNav = document.getElementById('btnToggleAutoAlign');\n    if (btnNav) {\n      btnNav.classList.toggle('active', isEnabled);\n      btnNav.title = isEnabled ? 'Auto Align: On (Click to turn off)' : 'Auto Align: Off (Click to turn on)';\n    }\n    const mobileBtn = document.getElementById('mobileBtnToggleAutoAlign');\n    if (mobileBtn) {\n      mobileBtn.classList.toggle('active', isEnabled);\n    }\n    const mobileText = document.getElementById('mobileAutoAlignText');\n    if (mobileText) {\n      mobileText.textContent = isEnabled ? 'Auto Align: ON' : 'Auto Align: OFF';\n    }\n  }\n\n  triggerLineUpElements(isAuto = false) {\n    const diag = this.getActiveDiagram();\n    if (!diag) return;\n    if (!this.canEditCurrentDiagram(diag)) return;\n    if (!isAuto) {\n      this.pushUndoState('Line up elements', diag.id);\n    }\n    this.normalizeNodeDimensions();\n    this.fixInvertedOutcomeBarriers();\n    this.layout.balanceDiagram(diag.id);\n    if (this.canvas) {\n      this.canvas.render();\n    }\n    if (!isAuto) {\n      this.showNotification('Diagram elements lined up!', 'success');\n      this.scheduleAutoSave('Lined up diagram elements');\n    } else {\n      this.scheduleAutoSave('Auto-aligned diagram elements');\n    }\n  }\n\n  isInstanceLicensedForEdit() {\n    if (this.workspace && this.workspace.license_status) {\n      if (this.workspace.license_status.is_onprem && !this.workspace.license_status.can_edit) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  showUnlicensedPrompt() {\n    const status = (this.workspace && this.workspace.license_status) || {};\n    const msg = status.message || 'Unlicensed On-Premise Instance: Creating or editing Bowtie diagrams requires an active license key.';\n    this.showNotification(msg, 'danger');\n    this.updateUnlicensedBannerUI();\n  }\n\n  updateUnlicensedBannerUI() {\n    const banner = document.getElementById('unlicensedBanner');\n    if (!banner) return;\n    const licensed = this.isInstanceLicensedForEdit();\n    if (!licensed) {\n      banner.style.display = 'flex';\n      const msgEl = document.getElementById('unlicensedBannerMessage');\n      if (msgEl && this.workspace && this.workspace.license_status && this.workspace.license_status.message) {\n        msgEl.textContent = this.workspace.license_status.message;\n      }\n    } else {\n      banner.style.display = 'none';\n    }\n  }\n\n  canEditCurrentDiagram(diag = null) {\n    if (!this.isInstanceLicensedForEdit()) return false;\n    const targetDiag = diag || this.getActiveDiagram();\n    if (!targetDiag) return true;\n    if (!this.currentUser) return true;\n    if (this.currentUser.is_admin) return true;\n\n    const userEmail = (this.currentUser.email || '').toLowerCase();\n    if ((targetDiag.created_by || '').toLowerCase() === userEmail) return true;\n\n    const perms = this.workspace && this.workspace.permissions ? this.workspace.permissions : [];\n    const diagRule = perms.find(p =>\n      ((p.target_type === 'diagram' && p.target_id === targetDiag.id) || p.target_type === 'all') &&\n      (p.user_email || '').toLowerCase() === userEmail\n    );\n    if (diagRule) {\n      return diagRule.access_level === 'write' || diagRule.access_level === 'admin';\n    }\n\n    if (targetDiag.folder_id) {\n      const folderRule = perms.find(p =>\n        p.target_type === 'folder' && p.target_id === targetDiag.folder_id &&\n        (p.user_email || '').toLowerCase() === userEmail\n      );\n      if (folderRule) {\n        return folderRule.access_level === 'write' || folderRule.access_level === 'admin';\n      }\n    }\n\n    return false;\n  }\n\n  canAdminCurrentDiagram(diag = null) {\n    const targetDiag = diag || this.getActiveDiagram();\n    if (!targetDiag) return true;\n    if (!this.currentUser) return true;\n    if (this.currentUser.is_admin) return true;\n\n    const userEmail = (this.currentUser.email || '').toLowerCase();\n    if ((targetDiag.created_by || '').toLowerCase() === userEmail) return true;\n\n    const perms = this.workspace && this.workspace.permissions ? this.workspace.permissions : [];\n    const diagRule = perms.find(p =>\n      ((p.target_type === 'diagram' && p.target_id === targetDiag.id) || p.target_type === 'all') &&\n      (p.user_email || '').toLowerCase() === userEmail\n    );\n    if (diagRule) {\n      return diagRule.access_level === 'admin';\n    }\n\n    if (targetDiag.folder_id) {\n      const folderRule = perms.find(p =>\n        p.target_type === 'folder' && p.target_id === targetDiag.folder_id &&\n        (p.user_email || '').toLowerCase() === userEmail\n      );\n      if (folderRule) {\n        return folderRule.access_level === 'admin';\n      }\n    }\n\n    return false;\n  }\n\n  updateDiagramPermissionUI() {\n    const canEdit = this.canEditCurrentDiagram();\n    const navBadge = document.getElementById('navReadOnlyBadge');\n    if (navBadge) {\n      navBadge.style.display = canEdit ? 'none' : 'inline-flex';\n    }\n\n    const saveStatus = document.getElementById('saveStatus');\n    if (saveStatus) {\n      if (!canEdit) {\n        saveStatus.style.display = 'none';\n      } else {\n        saveStatus.style.display = 'flex';\n      }\n    }\n\n    const mobileMenuStatus = document.getElementById('mobileMenuStatusText');\n    if (mobileMenuStatus) {\n      if (!canEdit) {\n        mobileMenuStatus.textContent = 'Permissions: Viewer (Read-only)';\n      } else {\n        mobileMenuStatus.textContent = 'All changes saved';\n      }\n    }\n\n    const toolIds = [\n      'toolAddThreat',\n      'toolAddBarrier',\n      'toolAddMitigation',\n      'toolAddOutcome',\n      'toolAddEscalation',\n      'toolDeleteNode'\n    ];\n    toolIds.forEach(id => {\n      const el = document.getElementById(id);\n      if (el) {\n        el.disabled = !canEdit;\n        el.style.opacity = canEdit ? '' : '0.4';\n        el.style.cursor = canEdit ? '' : 'not-allowed';\n      }\n    });\n\n    const mutateBtnIds = [\n      'btnAutoBalance',\n      'btnToggleAutoAlign',\n      'btnOrderBarriersTime',\n      'btnHeaderUndo',\n      'btnHeaderRedo',\n      'btnUndo',\n      'btnRedo'\n    ];\n    mutateBtnIds.forEach(id => {\n      const el = document.getElementById(id);\n      if (el) {\n        el.disabled = !canEdit;\n        el.style.opacity = canEdit ? '' : '0.4';\n      }\n    });\n\n    const mobileMutateIds = [\n      'mobileBtnOrderBarriers',\n      'mobileBtnAutoBalance',\n      'mobileBtnToggleAutoAlign'\n    ];\n    mobileMutateIds.forEach(id => {\n      const el = document.getElementById(id);\n      if (el) {\n        el.style.pointerEvents = canEdit ? '' : 'none';\n        el.style.opacity = canEdit ? '' : '0.4';\n      }\n    });\n\n    if (this.inspector && typeof this.inspector.render === 'function') {\n      this.inspector.render();\n    } else if (this.inspector && this.inspector.activeNode) {\n      this.inspector.inspect(this.inspector.activeNode.id);\n    }\n  }\n\n  toggleDiagramHistory() {\n    const dropdown = document.getElementById('diagramHistoryDropdown');\n    const backdrop = document.getElementById('diagramHistoryBackdrop');\n    if (!dropdown) return;\n    if (dropdown.style.display === 'none' || !dropdown.style.display) {\n      this.renderDiagramHistory();\n      dropdown.style.display = 'flex';\n      if (backdrop && window.innerWidth <= 900) {\n        backdrop.style.display = 'block';\n      }\n    } else {\n      dropdown.style.display = 'none';\n      if (backdrop) backdrop.style.display = 'none';\n    }\n  }\n\n  closeDiagramHistory() {\n    const dropdown = document.getElementById('diagramHistoryDropdown');\n    const backdrop = document.getElementById('diagramHistoryBackdrop');\n    if (dropdown) dropdown.style.display = 'none';\n    if (backdrop) backdrop.style.display = 'none';\n  }\n\n  renderDiagramHistory() {\n    const listEl = document.getElementById('diagramHistoryList');\n    const badgeEl = document.getElementById('historyCountBadge');\n    if (!listEl) return;\n\n    const diagId = this.currentDiagram ? this.currentDiagram.id : null;\n    const undoStack = this.getUndoStack(diagId);\n    const redoStack = this.getRedoStack(diagId);\n\n    if (badgeEl) {\n      badgeEl.textContent = undoStack.length;\n      badgeEl.style.display = undoStack.length > 0 ? 'inline-flex' : 'none';\n    }\n\n    if (undoStack.length === 0 && redoStack.length === 0) {\n      listEl.innerHTML = `\n        <div class=\"history-empty-state\">\n          <div class=\"history-empty-icon\">\n            <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 14 14\"/></svg>\n          </div>\n          <div style=\"font-weight:600; color:#334155; margin-bottom:4px;\">No History Recorded</div>\n          <div>Changes made to the active diagram will appear here.</div>\n        </div>\n      `;\n      return;\n    }\n\n    let html = '';\n\n    // 1. Redo items (newer states that can be restored going forward)\n    if (redoStack.length > 0) {\n      for (let i = redoStack.length - 1; i >= 0; i--) {\n        const item = redoStack[i];\n        const timeStr = this.formatRelativeTime(item.timestamp);\n        html += `\n          <button type=\"button\" class=\"history-entry-item is-redo\" data-redo-index=\"${i}\" title=\"Redo to this change\">\n            <div class=\"history-entry-icon\">\n              <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M21 7v6h-6\"/><path d=\"M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13\"/></svg>\n            </div>\n            <div class=\"history-entry-content\">\n              <div class=\"history-entry-title\">${this.escapeHtml(item.description || 'Edit')}</div>\n              <div class=\"history-entry-sub\">\n                <span>Redo change</span>\n                <span class=\"history-entry-time\">${timeStr}</span>\n              </div>\n            </div>\n          </button>\n        `;\n      }\n    }\n\n    // 2. Current diagram state marker\n    html += `\n      <div class=\"history-entry-item is-current\" title=\"Current active state\">\n        <div class=\"history-entry-icon\">\n          <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><circle cx=\"12\" cy=\"12\" r=\"8\"/></svg>\n        </div>\n        <div class=\"history-entry-content\">\n          <div class=\"history-entry-title\" style=\"color: #2563eb; font-weight:700;\">Current Diagram State</div>\n          <div class=\"history-entry-sub\">\n            <span>${undoStack.length} change${undoStack.length === 1 ? '' : 's'} recorded</span>\n          </div>\n        </div>\n      </div>\n    `;\n\n    // 3. Undo items (earlier states that can be reverted back to)\n    if (undoStack.length > 0) {\n      for (let i = undoStack.length - 1; i >= 0; i--) {\n        const item = undoStack[i];\n        const timeStr = this.formatRelativeTime(item.timestamp);\n        const stepsBack = undoStack.length - i;\n        html += `\n          <button type=\"button\" class=\"history-entry-item\" data-undo-index=\"${i}\" title=\"Revert back to state before this change (${stepsBack} step${stepsBack === 1 ? '' : 's'})\">\n            <div class=\"history-entry-icon\">\n              <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M3 7v6h6\"/><path d=\"M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13\"/></svg>\n            </div>\n            <div class=\"history-entry-content\">\n              <div class=\"history-entry-title\">${this.escapeHtml(item.description || 'Edit')}</div>\n              <div class=\"history-entry-sub\">\n                <span>Revert before this change</span>\n                <span class=\"history-entry-time\">${timeStr}</span>\n              </div>\n            </div>\n          </button>\n        `;\n      }\n    }\n\n    listEl.innerHTML = html;\n\n    // Attach click listeners to undo items\n    listEl.querySelectorAll('[data-undo-index]').forEach(btn => {\n      btn.addEventListener('click', (e) => {\n        e.stopPropagation();\n        const idx = parseInt(btn.getAttribute('data-undo-index'), 10);\n        this.jumpToUndoStep(idx);\n      });\n    });\n\n    // Attach click listeners to redo items\n    listEl.querySelectorAll('[data-redo-index]').forEach(btn => {\n      btn.addEventListener('click', (e) => {\n        e.stopPropagation();\n        const idx = parseInt(btn.getAttribute('data-redo-index'), 10);\n        this.jumpToRedoStep(idx);\n      });\n    });\n  }\n\n  jumpToUndoStep(targetIndex) {\n    const diagId = this.currentDiagram ? this.currentDiagram.id : null;\n    if (!diagId) return;\n    const undoStack = this.getUndoStack(diagId);\n    if (targetIndex < 0 || targetIndex >= undoStack.length) return;\n\n    const count = undoStack.length - targetIndex;\n    const targetDesc = undoStack[targetIndex].description;\n\n    for (let i = 0; i < count; i++) {\n      this.undo(true);\n    }\n    this.renderDiagramHistory();\n    this.showNotification(`Reverted to state before \"${targetDesc}\"`, 'info');\n  }\n\n  jumpToRedoStep(targetIndex) {\n    const diagId = this.currentDiagram ? this.currentDiagram.id : null;\n    if (!diagId) return;\n    const redoStack = this.getRedoStack(diagId);\n    if (targetIndex < 0 || targetIndex >= redoStack.length) return;\n\n    const count = redoStack.length - targetIndex;\n    const targetDesc = redoStack[targetIndex].description;\n\n    for (let i = 0; i < count; i++) {\n      this.redo(true);\n    }\n    this.renderDiagramHistory();\n    this.showNotification(`Advanced diagram to \"${targetDesc}\"`, 'info');\n  }\n\n  formatRelativeTime(ts) {\n    if (!ts) return '';\n    const diffSec = Math.floor((Date.now() - ts) / 1000);\n    if (diffSec < 10) return 'just now';\n    if (diffSec < 60) return `${diffSec}s ago`;\n    const diffMin = Math.floor(diffSec / 60);\n    if (diffMin < 60) return `${diffMin}m ago`;\n    const diffHours = Math.floor(diffMin / 60);\n    if (diffHours < 24) return `${diffHours}h ago`;\n    const d = new Date(ts);\n    return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;\n  }\n\n  // ==========================================================================\n  // COST VS PROTECTION FINANCIAL ANALYSIS MODAL\n  // ==========================================================================\n  openCostAnalysisModal() {\n    const modal = document.getElementById('modalCostAnalysis');\n    if (!modal) return;\n\n    const diag = this.getActiveDiagram();\n    const allNodes = this.workspace && this.workspace.nodes ? this.workspace.nodes : [];\n    const diagNodes = diag ? allNodes.filter(n => n.diagram_id === diag.id) : allNodes;\n\n    // Filter defensive barriers and mitigations\n    const defensiveNodes = diagNodes.filter(n =>\n      ['threat_barrier', 'outcome_barrier', 'mitigation'].includes(n.node_type)\n    );\n\n    let totalCost = 0;\n    let prevCost = 0;\n    let mitCost = 0;\n    let sceCount = 0;\n\n    const formatCurrency = (amount, cur = 'USD') => {\n      try {\n        return new Intl.NumberFormat('en-US', { style: 'currency', currency: cur, maximumFractionDigits: 0 }).format(amount);\n      } catch (e) {\n        return `$${Math.round(amount).toLocaleString()}`;\n      }\n    };\n\n    defensiveNodes.forEach(node => {\n      const cost = parseFloat(node.cost_estimate) || 0;\n      totalCost += cost;\n      if (node.node_type === 'threat_barrier') {\n        prevCost += cost;\n      } else {\n        mitCost += cost;\n      }\n      if (node.criticality === 'SCE' || node.criticality === 'High') {\n        sceCount++;\n      }\n    });\n\n    const prevNodes = defensiveNodes.filter(n => n.node_type === 'threat_barrier');\n    const mitNodes = defensiveNodes.filter(n => n.node_type !== 'threat_barrier');\n    const sceCoverage = defensiveNodes.length > 0 ? Math.round((sceCount / defensiveNodes.length) * 100) : 0;\n\n    // Update Scorecard Cards\n    const statTotal = document.getElementById('costStatTotal');\n    const statCount = document.getElementById('costStatElementCount');\n    const statPrev = document.getElementById('costStatPrevention');\n    const statPrevCount = document.getElementById('costStatPrevCount');\n    const statMit = document.getElementById('costStatMitigation');\n    const statMitCount = document.getElementById('costStatMitCount');\n    const statSceCoverage = document.getElementById('costStatSceCoverage');\n    const statSceCount = document.getElementById('costStatSceCount');\n\n    if (statTotal) statTotal.textContent = formatCurrency(totalCost);\n    if (statCount) statCount.textContent = `${defensiveNodes.length} barriers & mitigations`;\n    if (statPrev) statPrev.textContent = formatCurrency(prevCost);\n    if (statPrevCount) statPrevCount.textContent = `${prevNodes.length} threat barriers`;\n    if (statMit) statMit.textContent = formatCurrency(mitCost);\n    if (statMitCount) statMitCount.textContent = `${mitNodes.length} outcome barriers & mit`;\n    if (statSceCoverage) statSceCoverage.textContent = `${sceCoverage}%`;\n    if (statSceCount) statSceCount.textContent = `${sceCount} SCE barriers active`;\n\n    // Populate Detailed Matrix Table\n    const tbody = document.getElementById('costAnalysisTableBody');\n    if (tbody) {\n      tbody.innerHTML = '';\n      if (defensiveNodes.length === 0) {\n        tbody.innerHTML = `<tr><td colspan=\"6\" class=\"text-center text-muted\" style=\"padding: 24px;\">No safety barriers or mitigations defined on this diagram yet.</td></tr>`;\n      } else {\n        defensiveNodes.forEach(node => {\n          const cost = parseFloat(node.cost_estimate) || 0;\n          const currency = node.cost_currency || 'USD';\n          const eff = node.effectiveness || 'Medium';\n          const crit = node.criticality || 'Standard';\n          const isSce = crit === 'SCE';\n\n          // Type badge styling\n          let typeLabel = 'Threat Barrier';\n          let typeColor = '#2563eb';\n          if (node.node_type === 'outcome_barrier') {\n            typeLabel = 'Outcome Barrier';\n            typeColor = '#dc2626';\n          } else if (node.node_type === 'mitigation') {\n            typeLabel = 'Mitigation Control';\n            typeColor = '#d97706';\n          }\n\n          // Efficiency assessment\n          let assessmentBadge = '';\n          if (cost > 0) {\n            if (eff === 'High' && cost < 150000) {\n              assessmentBadge = `<span class=\"badge\" style=\"background:#10b981; color:#fff;\">High ROI (Optimum Control)</span>`;\n            } else if (eff === 'High') {\n              assessmentBadge = `<span class=\"badge\" style=\"background:#3b82f6; color:#fff;\">Capital SCE Control</span>`;\n            } else if (cost >= 150000) {\n              assessmentBadge = `<span class=\"badge\" style=\"background:#f59e0b; color:#fff;\">Review Cost Efficiency</span>`;\n            } else {\n              assessmentBadge = `<span class=\"badge\" style=\"background:#64748b; color:#fff;\">Balanced Standard Control</span>`;\n            }\n          } else {\n            assessmentBadge = `<span class=\"badge\" style=\"background:#475569; color:#94a3b8;\">Cost Estimate Pending</span>`;\n          }\n\n          const tr = document.createElement('tr');\n          tr.innerHTML = `\n            <td>\n              <div style=\"font-weight: 600; color: #f1f5f9;\">${this.escapeHtml(this.getNodeDisplayTitle(node))}</div>\n              <div class=\"text-muted text-xs\" style=\"max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\">${this.escapeHtml(node.description || '')}</div>\n            </td>\n            <td>\n              <span class=\"badge\" style=\"background: ${typeColor}; color: #ffffff;\">${typeLabel}</span>\n            </td>\n            <td>\n              <span class=\"badge\" style=\"background: ${eff === 'High' ? '#10b981' : (eff === 'Medium' ? '#f59e0b' : '#ef4444')}; color: #ffffff;\">\n                ${eff}\n              </span>\n            </td>\n            <td>\n              <span class=\"badge\" style=\"background: ${isSce ? '#dc2626' : '#334155'}; color: #ffffff;\">\n                ${isSce ? '★ SCE (Safety Critical)' : 'Standard'}\n              </span>\n            </td>\n            <td style=\"font-family: monospace; font-weight: 600; color: ${cost > 0 ? '#38bdf8' : '#94a3b8'};\">\n              ${cost > 0 ? `${formatCurrency(cost, currency)} <span class=\"text-xs text-muted\">(${currency})</span>` : '—'}\n            </td>\n            <td>${assessmentBadge}</td>\n          `;\n          tbody.appendChild(tr);\n        });\n      }\n    }\n\n    modal.classList.add('show');\n  }\n\n  exportCostCsv() {\n    const diag = this.getActiveDiagram();\n    const allNodes = this.workspace && this.workspace.nodes ? this.workspace.nodes : [];\n    const diagNodes = diag ? allNodes.filter(n => n.diagram_id === diag.id) : allNodes;\n    const defensiveNodes = diagNodes.filter(n =>\n      ['threat_barrier', 'outcome_barrier', 'mitigation'].includes(n.node_type)\n    );\n\n    const headers = ['Element Title', 'Node Type', 'Description', 'Effectiveness', 'Criticality', 'Cost Estimate', 'Currency', 'Responsible Role'];\n    const rows = defensiveNodes.map(n => [\n      `\"${(this.getNodeDisplayTitle(n) || '').replace(/\"/g, '\"\"')}\"`,\n      `\"${n.node_type}\"`,\n      `\"${(n.description || '').replace(/\"/g, '\"\"')}\"`,\n      `\"${n.effectiveness || 'Medium'}\"`,\n      `\"${n.criticality || 'Standard'}\"`,\n      n.cost_estimate || 0,\n      `\"${n.cost_currency || 'USD'}\"`,\n      `\"${(n.responsible_role || '').replace(/\"/g, '\"\"')}\"`\n    ]);\n\n    const csvContent = [headers.join(','), ...rows.map(r => r.join(','))].join('\\n');\n    const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });\n    const url = URL.createObjectURL(blob);\n    const a = document.createElement('a');\n    a.href = url;\n    a.download = `Bowtie_Cost_vs_Protection_${diag ? diag.title.replace(/\\s+/g, '_') : 'Workspace'}.csv`;\n    document.body.appendChild(a);\n    a.click();\n    document.body.removeChild(a);\n    URL.revokeObjectURL(url);\n    this.showNotification('Exported Cost vs Protection CSV report!', 'success');\n  }\n\n  // ==========================================================================\n  // USER SETTINGS MODAL: CUSTOM NAMES & COLORS\n  // ==========================================================================\n  openSettingsModal() {\n    const modal = document.getElementById('modalSettings');\n    const tbody = document.getElementById('settingsTableBody');\n    if (!tbody) return;\n\n    const allTypes = [\n      { key: 'hazard', defaultName: 'Hazard', defaultColor: '#eab308' },\n      { key: 'top_event', defaultName: 'Top event', defaultColor: '#f97316' },\n      { key: 'threat', defaultName: 'Threat', defaultColor: '#2563eb' },\n      { key: 'threat_barrier', defaultName: 'Threat Barrier', defaultColor: '#334155' },\n      { key: 'outcome_barrier', defaultName: 'Outcome Barrier', defaultColor: '#334155' },\n      { key: 'outcome', defaultName: 'Outcome', defaultColor: '#dc2626' },\n      { key: 'escalation_factor', defaultName: 'Escalation Factor', defaultColor: '#eab308' },\n      { key: 'mitigation', defaultName: 'Mitigation', defaultColor: '#64748b' },\n    ];\n\n    const currentNames = this.getCustomNames();\n    const currentColors = this.getCustomColors();\n\n    tbody.innerHTML = '';\n    allTypes.forEach(t => {\n      const nameVal = currentNames[t.key] || t.defaultName;\n      const colorVal = currentColors[t.key] || t.defaultColor;\n      const safeNameVal = this.escapeHtml(nameVal);\n      const safeColorVal = this.escapeHtml(colorVal);\n\n      const tr = document.createElement('tr');\n      tr.innerHTML = `\n        <td><strong>${this.escapeHtml(t.defaultName)}</strong></td>\n        <td>\n          <input type=\"text\" class=\"form-input setting-name-input\" data-key=\"${this.escapeHtml(t.key)}\" value=\"${safeNameVal}\" style=\"max-width: 220px;\">\n        </td>\n        <td>\n          <div class=\"color-picker-cell\">\n            <input type=\"color\" class=\"color-input-native setting-color-picker\" data-key=\"${this.escapeHtml(t.key)}\" value=\"${safeColorVal}\">\n            <input type=\"text\" class=\"form-input color-text-input setting-color-text\" data-key=\"${this.escapeHtml(t.key)}\" value=\"${safeColorVal}\">\n          </div>\n        </td>\n        <td>\n          <span class=\"node-preview-pill\" id=\"preview-${this.escapeHtml(t.key)}\" style=\"background-color: ${safeColorVal};\">\n            ${safeNameVal}\n          </span>\n        </td>\n      `;\n\n      // Live preview sync\n      const nameInput = tr.querySelector('.setting-name-input');\n      const colorPicker = tr.querySelector('.setting-color-picker');\n      const colorText = tr.querySelector('.setting-color-text');\n      const previewPill = tr.querySelector(`#preview-${t.key}`);\n\n      nameInput.addEventListener('input', () => {\n        previewPill.textContent = nameInput.value || t.defaultName;\n      });\n\n      colorPicker.addEventListener('input', () => {\n        colorText.value = colorPicker.value;\n        previewPill.style.backgroundColor = colorPicker.value;\n      });\n\n      colorText.addEventListener('input', () => {\n        if (/^#[0-9A-Fa-f]{6}$/.test(colorText.value)) {\n          colorPicker.value = colorText.value;\n          previewPill.style.backgroundColor = colorText.value;\n        }\n      });\n\n      tbody.appendChild(tr);\n    });\n\n    modal.classList.add('show');\n\n    // Save Settings Button\n    const btnSave = document.getElementById('btnSaveSettings');\n    btnSave.onclick = async () => {\n      const newNames = {};\n      const newColors = {};\n\n      tbody.querySelectorAll('.setting-name-input').forEach(input => {\n        newNames[input.getAttribute('data-key')] = input.value.trim();\n      });\n\n      tbody.querySelectorAll('.setting-color-picker').forEach(picker => {\n        newColors[picker.getAttribute('data-key')] = picker.value;\n      });\n\n      const updatedSettings = {\n        custom_names: newNames,\n        custom_colors: newColors,\n      };\n\n      try {\n        const res = await this.apiFetch('/api/user/settings', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify({ settings: updatedSettings }),\n        });\n\n        if (res.ok) {\n          if (!this.currentUser) this.currentUser = {};\n          this.currentUser.settings = updatedSettings;\n\n          // Re-render canvas and tree with new custom names and colors!\n          this.canvas.render();\n          this.tree.render();\n          modal.classList.remove('show');\n          this.showNotification('Custom node names and colors saved and applied!', 'success');\n        }\n      } catch (err) {\n        alert('Failed to save settings: ' + err.message);\n      }\n    };\n\n    // Reset Defaults Button\n    const btnReset = document.getElementById('btnResetSettingsDefault');\n    btnReset.onclick = () => {\n      allTypes.forEach(t => {\n        const n = tbody.querySelector(`.setting-name-input[data-key=\"${t.key}\"]`);\n        const c = tbody.querySelector(`.setting-color-picker[data-key=\"${t.key}\"]`);\n        const ct = tbody.querySelector(`.setting-color-text[data-key=\"${t.key}\"]`);\n        const prev = tbody.querySelector(`#preview-${t.key}`);\n        if (n) n.value = t.defaultName;\n        if (c) c.value = t.defaultColor;\n        if (ct) ct.value = t.defaultColor;\n        if (prev) {\n          prev.textContent = t.defaultName;\n          prev.style.backgroundColor = t.defaultColor;\n        }\n      });\n    };\n  }\n\n  // ==========================================================================\n  // VERSION HISTORY & AUDIT LOG MODAL (Google Sheets Style)\n  // ==========================================================================\n  async openHistoryModal() {\n    if (!this.currentUser || !this.currentUser.is_admin) {\n      this.showNotification('Access denied. Administrator privileges required to view the System Audit Log.', 'error');\n      return;\n    }\n    const modal = document.getElementById('modalHistory');\n    const tbody = document.getElementById('auditTableBody');\n    const searchInput = document.getElementById('auditSearchInput');\n\n    const loadEntries = async () => {\n      try {\n        const res = await this.apiFetch('/api/audit');\n        if (!res.ok) throw new Error('Failed to fetch audit log');\n        const data = await res.json();\n        const entries = data.entries || [];\n\n        const q = searchInput ? searchInput.value.toLowerCase().trim() : '';\n        const filtered = entries.filter(e => {\n          if (!q) return true;\n          return (e.user_email && e.user_email.toLowerCase().includes(q)) ||\n                 (e.summary && e.summary.toLowerCase().includes(q)) ||\n                 (e.action && e.action.toLowerCase().includes(q));\n        });\n\n        tbody.innerHTML = '';\n        if (filtered.length === 0) {\n          tbody.innerHTML = `<tr><td colspan=\"6\" class=\"text-center text-muted\" style=\"padding: 20px;\">No change log records found.</td></tr>`;\n          return;\n        }\n\n        filtered.forEach(entry => {\n          const tr = document.createElement('tr');\n          const dateStr = new Date(entry.timestamp).toLocaleString();\n          const hasSnapshot = !!entry.snapshot_id;\n          const actionBtnHtml = hasSnapshot\n            ? `<button class=\"btn btn-xs btn-outline btn-revert-version\" data-snapshot=\"${this.escapeHtml(entry.snapshot_id)}\" title=\"Revert workspace to this version\">↺ Restore</button>`\n            : `<span class=\"text-xs text-muted\" title=\"Historical baseline\">—</span>`;\n\n          tr.innerHTML = `\n            <td style=\"font-family: monospace; font-size: 11px; white-space: nowrap;\">${this.escapeHtml(dateStr)}</td>\n            <td><strong>${this.escapeHtml(entry.user_email)}</strong></td>\n            <td><span class=\"badge\" style=\"background:#1e293b;\">${this.escapeHtml(entry.action)}</span></td>\n            <td>${this.escapeHtml(entry.target_type)}</td>\n            <td>${this.escapeHtml(entry.summary)}</td>\n            <td style=\"text-align: center;\">${actionBtnHtml}</td>\n          `;\n\n          if (hasSnapshot) {\n            const btnRevert = tr.querySelector('.btn-revert-version');\n            if (btnRevert) {\n              btnRevert.onclick = async () => {\n                const confirmed = confirm(\n                  `Are you sure you want to revert the workspace to this version?\\n\\n` +\n                  `Timestamp: ${dateStr}\\n` +\n                  `Author: ${entry.user_email}\\n` +\n                  `Summary: ${entry.summary}\\n\\n` +\n                  `All diagrams and elements will be restored to this exact historical state.`\n                );\n                if (!confirmed) return;\n\n                try {\n                  const actorEmail = this.currentUser ? this.currentUser.email : 'user';\n                  const res = await this.apiFetch(`/api/audit/revert/${entry.snapshot_id}`, {\n                    method: 'POST',\n                    headers: { 'Content-Type': 'application/json' },\n                    body: JSON.stringify({ actor_email: actorEmail }),\n                  });\n\n                  if (res.ok) {\n                    this.workspace = await res.json();\n                    if (this.workspace.diagrams && this.workspace.diagrams.length > 0) {\n                      this.currentDiagram = this.workspace.diagrams[0];\n                    }\n                    this.initDiagramSelector();\n                    this.canvas.render();\n                    this.tree.render();\n                    if (this.documents) this.documents.render();\n                    this.updateAuditBadge();\n                    this.showNotification(`Workspace successfully restored to version from ${dateStr}!`, 'success');\n                    await loadEntries();\n                  } else {\n                    const errText = await res.text();\n                    alert('Failed to revert version: ' + errText);\n                  }\n                } catch (err) {\n                  alert('Error reverting version: ' + err.message);\n                }\n              };\n            }\n          }\n\n          tbody.appendChild(tr);\n        });\n      } catch (err) {\n        tbody.innerHTML = `<tr><td colspan=\"6\" class=\"text-danger\">Error loading audit trail: ${err.message}</td></tr>`;\n      }\n    };\n\n    if (searchInput) searchInput.oninput = () => loadEntries();\n    const btnRefresh = document.getElementById('btnRefreshAudit');\n    if (btnRefresh) btnRefresh.onclick = () => loadEntries();\n\n    await loadEntries();\n    modal.classList.add('show');\n  }\n\n  // ==========================================================================\n  // ==========================================================================\n  // SHARE & PERMISSIONS MODAL (Google Drive Sharing Layout & Experience)\n  // ==========================================================================\n  openShareModal() {\n    this.shareTargetScope = 'diagram';\n    this.shareTargetId = this.currentDiagram ? this.currentDiagram.id : 'all';\n    this.resetShareInvitePanel();\n    this.populateShareModal();\n    const modal = document.getElementById('modalShare');\n    if (modal) modal.classList.add('show');\n  }\n\n  openShareModalForFolder(folder) {\n    this.shareTargetScope = 'folder';\n    this.shareTargetFolder = folder;\n    this.shareTargetId = folder ? folder.id : '';\n    this.resetShareInvitePanel();\n    this.populateShareModal();\n\n    const select = document.getElementById('permTargetFolder');\n    if (select && folder) select.value = folder.id;\n\n    const modal = document.getElementById('modalShare');\n    if (modal) modal.classList.add('show');\n  }\n\n  openShareModalForDiagram(diag) {\n    if (diag) this.currentDiagram = diag;\n    this.shareTargetScope = 'diagram';\n    this.shareTargetId = diag ? diag.id : (this.currentDiagram ? this.currentDiagram.id : 'all');\n    this.resetShareInvitePanel();\n    this.populateShareModal();\n    const modal = document.getElementById('modalShare');\n    if (modal) modal.classList.add('show');\n  }\n\n  openShareModalForElement(node) {\n    this.shareTargetScope = 'element';\n    this.shareTargetElement = node;\n    this.shareTargetId = node ? (node.global_element_id || node.id) : '';\n    this.resetShareInvitePanel();\n    this.populateShareModal();\n\n    const select = document.getElementById('permTargetElement');\n    if (select && node) select.value = node.global_element_id || node.id;\n\n    const modal = document.getElementById('modalShare');\n    if (modal) modal.classList.add('show');\n  }\n\n  resetShareInvitePanel() {\n    const invitePanel = document.getElementById('addPermissionForm');\n    const searchBox = document.getElementById('shareAddPeopleBox');\n    const emailInput = document.getElementById('permUserEmail');\n    const autocompleteList = document.getElementById('permUserAutocompleteList');\n    const noteInput = document.getElementById('shareInviteNote');\n\n    if (invitePanel) invitePanel.style.display = 'none';\n    if (searchBox) searchBox.style.display = 'block';\n    if (emailInput) {\n      emailInput.value = '';\n      emailInput.classList.remove('input-error');\n    }\n    if (autocompleteList) autocompleteList.style.display = 'none';\n    if (noteInput) noteInput.value = '';\n    this.selectedInviteUser = null;\n  }\n\n  populateShareModal() {\n    // 1. Dynamic Title (Google Drive style: Share 'Document Name')\n    const titleEl = document.getElementById('shareModalTitle');\n    if (titleEl) {\n      if (this.shareTargetScope === 'folder' && this.shareTargetFolder) {\n        titleEl.textContent = `Share folder '${this.shareTargetFolder.name}'`;\n      } else if (this.shareTargetScope === 'element' && this.shareTargetElement) {\n        titleEl.textContent = `Share element '${this.shareTargetElement.title}'`;\n      } else if (this.currentDiagram) {\n        titleEl.textContent = `Share '${this.currentDiagram.title}'`;\n      } else {\n        titleEl.textContent = `Share document`;\n      }\n    }\n\n    // 2. Sync Scope Selectors\n    const targetTypeSelect = document.getElementById('permTargetTypeSelect');\n    if (targetTypeSelect) {\n      targetTypeSelect.value = this.shareTargetScope || 'diagram';\n    }\n    const radio = document.querySelector(`input[name=\"permTargetType\"][value=\"${this.shareTargetScope || 'diagram'}\"]`);\n    if (radio) radio.checked = true;\n\n    const folderGroup = document.getElementById('permFolderSelectGroup');\n    const elemGroup = document.getElementById('permElementSelectGroup');\n    if (folderGroup) folderGroup.style.display = (this.shareTargetScope === 'folder') ? 'flex' : 'none';\n    if (elemGroup) elemGroup.style.display = (this.shareTargetScope === 'element') ? 'flex' : 'none';\n\n    // Populate Folders dropdown\n    const folderSelect = document.getElementById('permTargetFolder');\n    if (folderSelect) {\n      folderSelect.innerHTML = '';\n      (this.workspace.folders || []).forEach(f => {\n        const opt = document.createElement('option');\n        opt.value = f.id;\n        opt.textContent = `📁 ${f.name}`;\n        folderSelect.appendChild(opt);\n      });\n      if (this.shareTargetFolder) folderSelect.value = this.shareTargetFolder.id;\n    }\n\n    // Populate Elements dropdown\n    const elemSelect = document.getElementById('permTargetElement');\n    if (elemSelect) {\n      elemSelect.innerHTML = '';\n      (this.workspace.nodes || []).forEach(n => {\n        const opt = document.createElement('option');\n        opt.value = n.global_element_id || n.id;\n        opt.textContent = `${n.title}: ${n.description.substring(0, 35)}`;\n        elemSelect.appendChild(opt);\n      });\n      if (this.shareTargetElement) elemSelect.value = this.shareTargetElement.global_element_id || this.shareTargetElement.id;\n    }\n\n    // Scope select change handler\n    if (targetTypeSelect) {\n      targetTypeSelect.onchange = () => {\n        const val = targetTypeSelect.value;\n        this.shareTargetScope = val;\n        if (folderGroup) folderGroup.style.display = (val === 'folder') ? 'flex' : 'none';\n        if (elemGroup) elemGroup.style.display = (val === 'element') ? 'flex' : 'none';\n        const r = document.querySelector(`input[name=\"permTargetType\"][value=\"${val}\"]`);\n        if (r) r.checked = true;\n      };\n    }\n\n    // 3. Render People with Access List (Google Drive layout)\n    const userListEl = document.getElementById('sharePeopleWithAccessList');\n    if (userListEl) {\n      userListEl.innerHTML = '';\n\n      // Determine Owner\n      let ownerEmail = '';\n      if (this.shareTargetScope === 'folder' && this.shareTargetFolder) {\n        ownerEmail = this.shareTargetFolder.created_by || (this.currentUser ? this.currentUser.email : '');\n      } else if (this.currentDiagram) {\n        ownerEmail = this.currentDiagram.created_by || (this.currentUser ? this.currentUser.email : '');\n      } else {\n        ownerEmail = this.currentUser ? this.currentUser.email : 'admin@bowtierisk.com';\n      }\n      if (!ownerEmail && this.currentUser) ownerEmail = this.currentUser.email;\n\n      const ownerEmailLower = (ownerEmail || '').toLowerCase();\n      const dirOwner = (this.directoryUsersMap && this.directoryUsersMap[ownerEmailLower]) ||\n                       (this.workspace.users || []).find(u => u.email.toLowerCase() === ownerEmailLower);\n      const isOwnerMe = this.currentUser && this.currentUser.email.toLowerCase() === ownerEmailLower;\n\n      let ownerDisplayName = dirOwner ? (dirOwner.name || ownerEmail) : ownerEmail;\n      if (isOwnerMe) {\n        ownerDisplayName = dirOwner && dirOwner.name ? `${dirOwner.name} (you)` : `${ownerEmail} (you)`;\n      }\n\n      const ownerUser = dirOwner || this.getUserByEmail(ownerEmail) || { email: ownerEmail, name: ownerDisplayName };\n      const ownerAvatarHtml = this.getUserAvatarHtml(ownerUser, 36);\n\n      // Append Owner Row\n      const ownerRow = document.createElement('div');\n      ownerRow.className = 'gdrive-user-row';\n      ownerRow.innerHTML = `\n        <div class=\"gdrive-user-left\">\n          <div class=\"gdrive-user-avatar-wrap\">\n            ${ownerAvatarHtml}\n          </div>\n          <div class=\"gdrive-user-meta\">\n            <span class=\"gdrive-user-name\">${this.escapeHtml(ownerDisplayName)}</span>\n            <span class=\"gdrive-user-email\">${this.escapeHtml(ownerEmail)}</span>\n          </div>\n        </div>\n        <div class=\"gdrive-user-right\">\n          <span class=\"gdrive-owner-label\">Owner</span>\n        </div>\n      `;\n      userListEl.appendChild(ownerRow);\n\n      // Append Collaborator Rows\n      const perms = this.workspace.permissions || [];\n      const relevantPerms = perms.filter(p => {\n        // Exclude owner row duplicate\n        if (p.user_email && p.user_email.toLowerCase() === ownerEmailLower) return false;\n\n        // Filter based on active target\n        if (this.shareTargetScope === 'folder' && this.shareTargetFolder) {\n          return p.target_type === 'folder' && p.target_id === this.shareTargetFolder.id;\n        } else if (this.shareTargetScope === 'element' && this.shareTargetElement) {\n          const elId = this.shareTargetElement.global_element_id || this.shareTargetElement.id;\n          return p.target_type === 'element' && p.target_id === elId;\n        } else if (this.currentDiagram) {\n          return (p.target_type === 'diagram' && p.target_id === this.currentDiagram.id) ||\n                 (this.currentDiagram.folder_id && p.target_type === 'folder' && p.target_id === this.currentDiagram.folder_id) ||\n                 p.target_id === 'all';\n        }\n        return true;\n      });\n\n      relevantPerms.forEach(p => {\n        const userEmailLower = (p.user_email || '').toLowerCase();\n        const dirUser = (this.directoryUsersMap && this.directoryUsersMap[userEmailLower]) ||\n                        (this.workspace.users || []).find(u => u.email.toLowerCase() === userEmailLower);\n\n        const displayName = dirUser ? (dirUser.name || p.user_email) : p.user_email;\n\n        let scopeBadge = '';\n        if (p.target_type === 'folder') {\n          scopeBadge = `<span class=\"badge\" style=\"background: #e0f2fe; color: #0369a1; font-size: 10px; font-weight: 500; padding: 1px 5px; margin-left: 6px;\">📁 Folder</span>`;\n        } else if (p.target_type === 'element') {\n          scopeBadge = `<span class=\"badge\" style=\"background: #fef3c7; color: #92400e; font-size: 10px; font-weight: 500; padding: 1px 5px; margin-left: 6px;\">🛡️ Element</span>`;\n        }\n\n        const collabUser = dirUser || this.getUserByEmail(p.user_email) || { email: p.user_email, name: displayName };\n        const collabAvatarHtml = this.getUserAvatarHtml(collabUser, 36);\n\n        const userRow = document.createElement('div');\n        userRow.className = 'gdrive-user-row';\n        userRow.innerHTML = `\n          <div class=\"gdrive-user-left\">\n            <div class=\"gdrive-user-avatar-wrap\">\n              ${collabAvatarHtml}\n            </div>\n            <div class=\"gdrive-user-meta\">\n              <span class=\"gdrive-user-name\">\n                ${this.escapeHtml(displayName)}\n                ${scopeBadge}\n              </span>\n              <span class=\"gdrive-user-email\">${this.escapeHtml(p.user_email)}</span>\n            </div>\n          </div>\n          <div class=\"gdrive-user-right\">\n            <select class=\"gdrive-collaborator-role-select\" data-perm-id=\"${p.id}\" data-target-type=\"${p.target_type}\" data-target-id=\"${p.target_id}\" data-email=\"${this.escapeHtml(p.user_email)}\">\n              <option value=\"read\" ${p.access_level === 'read' ? 'selected' : ''}>Viewer</option>\n              <option value=\"write\" ${p.access_level === 'write' ? 'selected' : ''}>Editor</option>\n              <option value=\"admin\" ${p.access_level === 'admin' ? 'selected' : ''}>Admin</option>\n              <option value=\"remove\" style=\"color: #d93025;\">Remove access</option>\n            </select>\n          </div>\n        `;\n\n        const roleSelect = userRow.querySelector('.gdrive-collaborator-role-select');\n        if (roleSelect) {\n          roleSelect.onchange = async () => {\n            const newRole = roleSelect.value;\n            if (newRole === 'remove') {\n              await this.removePermission(p.id);\n              this.populateShareModal();\n            } else {\n              try {\n                const res = await this.apiFetch('/api/share', {\n                  method: 'POST',\n                  headers: { 'Content-Type': 'application/json' },\n                  body: JSON.stringify({\n                    target_type: p.target_type,\n                    target_id: p.target_id,\n                    user_email: p.user_email,\n                    access_level: newRole,\n                    actor_email: this.currentUser ? this.currentUser.email : 'admin@bowtierisk.com',\n                  }),\n                });\n                if (res.ok) {\n                  const wsRes = await this.apiFetch('/api/workspace');\n                  if (wsRes.ok) this.workspace = await wsRes.json();\n                  this.populateShareModal();\n                  if (this.documents) this.documents.render();\n                  this.showNotification(`Updated ${p.user_email} to ${newRole}!`, 'success');\n                } else {\n                  const errText = await res.text();\n                  alert('Failed to update permission: ' + errText);\n                  roleSelect.value = p.access_level;\n                }\n              } catch (e) {\n                alert('Error updating permission: ' + e.message);\n                roleSelect.value = p.access_level;\n              }\n            }\n          };\n        }\n\n        userListEl.appendChild(userRow);\n      });\n    }\n\n    // 4. Copy Link Button Handler\n    const copyLinkBtn = document.getElementById('btnShareCopyLink');\n    if (copyLinkBtn) {\n      copyLinkBtn.onclick = async () => {\n        const url = window.location.origin + window.location.pathname +\n                    (this.currentDiagram ? '?diagram=' + encodeURIComponent(this.currentDiagram.id) : '');\n        try {\n          if (navigator.clipboard && navigator.clipboard.writeText) {\n            await navigator.clipboard.writeText(url);\n          } else {\n            const input = document.createElement('input');\n            input.value = url;\n            document.body.appendChild(input);\n            input.select();\n            document.execCommand('copy');\n            document.body.removeChild(input);\n          }\n          copyLinkBtn.innerHTML = `\n            <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#059669\" stroke-width=\"2.5\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n            <span style=\"color: #059669; font-weight: 600;\">Link copied</span>\n          `;\n          this.showNotification('Link copied to clipboard', 'info');\n          setTimeout(() => {\n            copyLinkBtn.innerHTML = `\n              <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg>\n              <span>Copy link</span>\n            `;\n          }, 2500);\n        } catch (e) {\n          prompt('Copy link:', url);\n        }\n      };\n    }\n\n    // 5. General Access Selector Handler\n    const generalSelect = document.getElementById('shareGeneralAccessSelect');\n    const generalDesc = document.getElementById('shareGeneralAccessDesc');\n    if (generalSelect && generalDesc) {\n      generalSelect.onchange = () => {\n        if (generalSelect.value === 'organization') {\n          generalDesc.textContent = 'Anyone in your organization with the link can view';\n        } else {\n          generalDesc.textContent = 'Only people with access can open with the link';\n        }\n      };\n    }\n\n    // 6. Help & Settings Buttons\n    const btnHelp = document.getElementById('btnShareHelp');\n    if (btnHelp) {\n      btnHelp.onclick = () => {\n        this.showNotification('Sharing permissions: Viewers can inspect diagrams, Editors can modify elements, Admins have full access and can share.', 'info');\n      };\n    }\n    const btnSettings = document.getElementById('btnShareSettings');\n    if (btnSettings) {\n      btnSettings.onclick = () => {\n        this.showNotification('Sharing settings: Link sharing is currently restricted to authorized organization users.', 'info');\n      };\n    }\n\n    // 7. Invite Form Submission\n    const form = document.getElementById('addPermissionForm');\n    if (form) {\n      form.onsubmit = async (e) => {\n        e.preventDefault();\n        const targetType = document.getElementById('permTargetTypeSelect')?.value ||\n                           document.querySelector('input[name=\"permTargetType\"]:checked')?.value || 'diagram';\n        let targetId = 'all';\n\n        if (targetType === 'diagram') {\n          targetId = this.currentDiagram ? this.currentDiagram.id : 'all';\n        } else if (targetType === 'folder') {\n          const folderSelect = document.getElementById('permTargetFolder');\n          targetId = folderSelect ? folderSelect.value : '';\n        } else if (targetType === 'element') {\n          const elemSelect = document.getElementById('permTargetElement');\n          targetId = elemSelect ? elemSelect.value : '';\n        }\n\n        const emailInput = document.getElementById('permUserEmail');\n        const userEmail = (this.selectedInviteUser ? this.selectedInviteUser.email : (emailInput ? emailInput.value.trim() : ''));\n        if (!userEmail || !userEmail.includes('@')) {\n          this.showNotification('Please enter a valid user email address', 'warning');\n          if (emailInput) {\n            emailInput.classList.add('input-error');\n            emailInput.focus();\n          }\n          return;\n        }\n        const accessLevel = document.getElementById('permAccessLevel')?.value || 'read';\n\n        try {\n          const res = await this.apiFetch('/api/share', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({\n              target_type: targetType,\n              target_id: targetId,\n              user_email: userEmail,\n              access_level: accessLevel,\n              actor_email: this.currentUser ? this.currentUser.email : 'admin@bowtierisk.com',\n            }),\n          });\n\n          if (res.ok) {\n            const wsRes = await this.apiFetch('/api/workspace');\n            if (wsRes.ok) this.workspace = await wsRes.json();\n            this.resetShareInvitePanel();\n            this.populateShareModal();\n            if (this.documents) this.documents.render();\n            this.showNotification(`Granted ${accessLevel} permission to ${userEmail}!`, 'success');\n          } else {\n            const err = await res.text();\n            alert('Failed to grant permission: ' + err);\n          }\n        } catch (err) {\n          alert('Failed to grant permission: ' + err.message);\n        }\n      };\n    }\n\n    // 8. Invite Panel Cancel / Clear Buttons\n    const btnCancel = document.getElementById('btnCancelInvite');\n    if (btnCancel) {\n      btnCancel.onclick = () => this.resetShareInvitePanel();\n    }\n    const btnClearChip = document.getElementById('btnClearRecipient');\n    if (btnClearChip) {\n      btnClearChip.onclick = () => this.resetShareInvitePanel();\n    }\n  }\n\n  async removePermission(permId) {\n    try {\n      const res = await this.apiFetch(`/api/share/${permId}`, { method: 'DELETE' });\n      if (res.ok) {\n        const wsRes = await this.apiFetch('/api/workspace');\n        if (wsRes.ok) this.workspace = await wsRes.json();\n        if (this.documents) this.documents.render();\n        this.showNotification('Permission rule removed.', 'info');\n      }\n    } catch (e) {\n      alert('Error removing permission: ' + e.message);\n    }\n  }\n\n  initShareAutocomplete() {\n    const input = document.getElementById('permUserEmail');\n    const dropdown = document.getElementById('permUserAutocompleteList');\n    if (!input || !dropdown) return;\n\n    let debounceTimer = null;\n    let activeIndex = -1;\n    let currentResults = [];\n\n    const openInvitePanelForUser = (userOrEmail) => {\n      let email = typeof userOrEmail === 'string' ? userOrEmail : userOrEmail.email;\n      let displayName = typeof userOrEmail === 'string' ? userOrEmail : (userOrEmail.name || userOrEmail.email);\n\n      this.selectedInviteUser = typeof userOrEmail === 'object' ? userOrEmail : { email };\n      input.value = email;\n\n      const chipEl = document.getElementById('shareRecipientChip');\n      if (chipEl) {\n        const userForAvatar = this.selectedInviteUser || this.getUserByEmail(email) || { email, name: displayName };\n        const avatarHtml = this.getUserAvatarHtml(userForAvatar, 20);\n        chipEl.innerHTML = `\n          <span style=\"display: inline-flex; align-items: center; margin-right: 4px; border-radius: 50%; overflow: hidden;\">${avatarHtml}</span>\n          <span id=\"shareRecipientChipText\">${this.escapeHtml(displayName)}</span>\n          <button type=\"button\" id=\"btnClearRecipient\" class=\"btn-chip-remove\" title=\"Remove recipient\">✕</button>\n        `;\n        const btnClear = document.getElementById('btnClearRecipient');\n        if (btnClear) {\n          btnClear.addEventListener('click', () => {\n            const invitePanel = document.getElementById('addPermissionForm');\n            const searchBox = document.getElementById('shareAddPeopleBox');\n            if (invitePanel) invitePanel.style.display = 'none';\n            if (searchBox) searchBox.style.display = 'block';\n            input.value = '';\n            this.selectedInviteUser = null;\n            input.focus();\n          });\n        }\n      }\n\n      const invitePanel = document.getElementById('addPermissionForm');\n      const searchBox = document.getElementById('shareAddPeopleBox');\n      if (invitePanel) invitePanel.style.display = 'block';\n      if (searchBox) searchBox.style.display = 'none';\n\n      dropdown.style.display = 'none';\n      const noteInput = document.getElementById('shareInviteNote');\n      if (noteInput) noteInput.focus();\n    };\n\n    const fetchSuggestions = async (query) => {\n      try {\n        const res = await this.apiFetch(`/api/users/directory?q=${encodeURIComponent(query)}`);\n        if (!res.ok) {\n          dropdown.style.display = 'none';\n          return;\n        }\n        const data = await res.json();\n        if (!data.enabled) {\n          dropdown.style.display = 'none';\n          return;\n        }\n\n        if (!this.directoryUsersMap) this.directoryUsersMap = {};\n        (data.users || []).forEach(u => {\n          this.directoryUsersMap[u.email.toLowerCase()] = u;\n        });\n\n        currentResults = data.users || [];\n        activeIndex = -1;\n\n        if (currentResults.length === 0) {\n          if (query.trim().length > 0) {\n            dropdown.innerHTML = `<div class=\"text-xs text-muted\" style=\"padding: 10px 14px;\">No directory users found matching \"${this.escapeHtml(query)}\"</div>`;\n            dropdown.style.display = 'block';\n          } else {\n            dropdown.style.display = 'none';\n          }\n          return;\n        }\n\n        dropdown.innerHTML = currentResults.map((u, i) => `\n          <div class=\"user-autocomplete-item\" data-index=\"${i}\" data-email=\"${this.escapeHtml(u.email)}\">\n            <div class=\"user-autocomplete-avatar-wrap\">\n              ${this.getUserAvatarHtml(u, 28)}\n            </div>\n            <div class=\"user-autocomplete-info\">\n              <div class=\"user-autocomplete-name\">${this.escapeHtml(u.name || u.email)}</div>\n              <div class=\"user-autocomplete-email\">${this.escapeHtml(u.email)}</div>\n            </div>\n            ${u.role ? `<div class=\"user-autocomplete-role\" title=\"${this.escapeHtml(u.role)}\">${this.escapeHtml(u.role)}</div>` : ''}\n          </div>\n        `).join('');\n\n        dropdown.style.display = 'block';\n\n        dropdown.querySelectorAll('.user-autocomplete-item').forEach(item => {\n          item.addEventListener('click', () => {\n            const idx = parseInt(item.getAttribute('data-index'), 10);\n            const user = currentResults[idx] || { email: item.getAttribute('data-email') };\n            openInvitePanelForUser(user);\n          });\n        });\n      } catch (e) {\n        console.error(e);\n      }\n    };\n\n    input.addEventListener('input', () => {\n      clearTimeout(debounceTimer);\n      const val = input.value.trim();\n      debounceTimer = setTimeout(() => {\n        fetchSuggestions(val);\n      }, 150);\n    });\n\n    input.addEventListener('focus', () => {\n      fetchSuggestions(input.value.trim());\n    });\n\n    input.addEventListener('keydown', (e) => {\n      if (e.key === 'Enter') {\n        e.preventDefault();\n        if (activeIndex >= 0 && activeIndex < currentResults.length) {\n          openInvitePanelForUser(currentResults[activeIndex]);\n        } else if (input.value.trim().includes('@')) {\n          openInvitePanelForUser(input.value.trim());\n        }\n        return;\n      }\n\n      if (dropdown.style.display === 'none' || currentResults.length === 0) return;\n\n      const items = dropdown.querySelectorAll('.user-autocomplete-item');\n      if (e.key === 'ArrowDown') {\n        e.preventDefault();\n        activeIndex = (activeIndex + 1) % items.length;\n        items.forEach((it, idx) => it.classList.toggle('active', idx === activeIndex));\n        items[activeIndex]?.scrollIntoView({ block: 'nearest' });\n      } else if (e.key === 'ArrowUp') {\n        e.preventDefault();\n        activeIndex = (activeIndex - 1 + items.length) % items.length;\n        items.forEach((it, idx) => it.classList.toggle('active', idx === activeIndex));\n        items[activeIndex]?.scrollIntoView({ block: 'nearest' });\n      } else if (e.key === 'Escape') {\n        dropdown.style.display = 'none';\n      }\n    });\n\n    document.addEventListener('click', (e) => {\n      if (!e.target.closest('#shareAddPeopleBox') && !e.target.closest('.user-autocomplete-dropdown')) {\n        dropdown.style.display = 'none';\n      }\n    });\n  }\n\n  async ensureDirectoryLoaded() {\n    if (this.directoryLoaded) return;\n    try {\n      const res = await this.apiFetch('/api/users/directory');\n      if (res.ok) {\n        const data = await res.json();\n        if (data.enabled) {\n          if (!this.directoryUsersMap) this.directoryUsersMap = {};\n          (data.users || []).forEach(u => {\n            this.directoryUsersMap[u.email.toLowerCase()] = u;\n          });\n          this.directoryLoaded = true;\n          // Re-populate permissions if share modal is open\n          const modalShare = document.getElementById('modalShare');\n          if (modalShare && modalShare.classList.contains('show')) {\n            this.populateShareModal();\n          }\n          if (this.documents) {\n            this.documents.render();\n          }\n        }\n      }\n    } catch (e) {}\n  }\n\n  // ==========================================================================\n  // USER SIGN-IN & SIGN-UP AUTHENTICATION\n  // ==========================================================================\n  openAuthModal() {\n    const modal = document.getElementById('modalAuth');\n    if (!modal) return;\n\n    // Tabs Switcher & Panes\n    const tabsContainer = document.getElementById('modalAuthTabs');\n    const tabLogin = document.getElementById('tabAuthLoginBtn');\n    const tabSignup = document.getElementById('tabAuthSignupBtn');\n    const formLogin = document.getElementById('authLoginForm');\n    const formSignup = document.getElementById('authSignupForm');\n    const formForgot = document.getElementById('modalForgotForm');\n    const formReset = document.getElementById('modalResetForm');\n    const linkToSignup = document.getElementById('linkSwitchToSignup');\n    const linkToLogin = document.getElementById('linkSwitchToLogin');\n    const errBox = document.getElementById('modalAuthError');\n    const successBox = document.getElementById('modalAuthSuccess');\n\n    const clearMessages = () => {\n      if (errBox) { errBox.style.display = 'none'; errBox.textContent = ''; }\n      if (successBox) { successBox.style.display = 'none'; successBox.textContent = ''; }\n    };\n\n    const showLoginTab = () => {\n      clearMessages();\n      if (tabsContainer) tabsContainer.style.display = 'flex';\n      if (tabLogin) tabLogin.classList.add('active');\n      if (tabSignup) tabSignup.classList.remove('active');\n      if (formLogin) formLogin.style.display = 'block';\n      if (formSignup) formSignup.style.display = 'none';\n      if (formForgot) formForgot.style.display = 'none';\n      if (formReset) formReset.style.display = 'none';\n    };\n\n    const showSignupTab = () => {\n      clearMessages();\n      if (tabsContainer) tabsContainer.style.display = 'flex';\n      if (tabSignup) tabSignup.classList.add('active');\n      if (tabLogin) tabLogin.classList.remove('active');\n      if (formSignup) formSignup.style.display = 'block';\n      if (formLogin) formLogin.style.display = 'none';\n      if (formForgot) formForgot.style.display = 'none';\n      if (formReset) formReset.style.display = 'none';\n    };\n\n    const showForgotTab = (emailVal = '') => {\n      clearMessages();\n      if (tabsContainer) tabsContainer.style.display = 'none';\n      if (formLogin) formLogin.style.display = 'none';\n      if (formSignup) formSignup.style.display = 'none';\n      if (formForgot) formForgot.style.display = 'block';\n      if (formReset) formReset.style.display = 'none';\n      const input = document.getElementById('modalForgotEmail');\n      if (input) {\n        if (emailVal) input.value = emailVal;\n        input.focus();\n      }\n    };\n\n    const showResetTab = (emailVal = '', initialMessage = '') => {\n      clearMessages();\n      if (tabsContainer) tabsContainer.style.display = 'none';\n      if (formLogin) formLogin.style.display = 'none';\n      if (formSignup) formSignup.style.display = 'none';\n      if (formForgot) formForgot.style.display = 'none';\n      if (formReset) formReset.style.display = 'block';\n      const input = document.getElementById('modalResetEmail');\n      if (input) input.value = emailVal;\n      if (initialMessage && successBox) {\n        successBox.textContent = initialMessage;\n        successBox.style.display = 'block';\n      }\n      const codeInput = document.getElementById('modalResetCode');\n      if (codeInput) {\n        codeInput.value = '';\n        codeInput.focus();\n      }\n    };\n\n    if (tabLogin) tabLogin.onclick = showLoginTab;\n    if (tabSignup) tabSignup.onclick = showSignupTab;\n    if (linkToSignup) linkToSignup.onclick = (e) => { e.preventDefault(); showSignupTab(); };\n    if (linkToLogin) linkToLogin.onclick = (e) => { e.preventDefault(); showLoginTab(); };\n\n    const linkForgot = document.getElementById('linkModalForgotPassword');\n    if (linkForgot) {\n      linkForgot.onclick = (e) => {\n        e.preventDefault();\n        const curEmail = (document.getElementById('authLoginEmail')?.value || '').trim();\n        showForgotTab(curEmail);\n      };\n    }\n\n    const linkBackFromForgot = document.getElementById('linkModalBackToLoginFromForgot');\n    if (linkBackFromForgot) {\n      linkBackFromForgot.onclick = (e) => { e.preventDefault(); showLoginTab(); };\n    }\n\n    const linkBackFromReset = document.getElementById('linkModalBackToLoginFromReset');\n    if (linkBackFromReset) {\n      linkBackFromReset.onclick = (e) => { e.preventDefault(); showLoginTab(); };\n    }\n\n    const linkResend = document.getElementById('linkModalResendCode');\n    if (linkResend) {\n      linkResend.onclick = (e) => {\n        e.preventDefault();\n        const curEmail = (document.getElementById('modalResetEmail')?.value || '').trim();\n        showForgotTab(curEmail);\n      };\n    }\n\n    // Default to login tab\n    showLoginTab();\n\n    const emailInput = document.getElementById('authLoginEmail');\n    if (this.currentUser && emailInput && this.currentUser.email !== 'guest@bowtierisk.com') {\n      emailInput.value = this.currentUser.email;\n    }\n\n    // Handle Login Form\n    if (formLogin) {\n      formLogin.onsubmit = async (e) => {\n        e.preventDefault();\n        const email = document.getElementById('authLoginEmail').value.trim();\n        const password = document.getElementById('authLoginPassword').value;\n        clearMessages();\n\n        try {\n          const res = await fetch('/api/auth/login', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ email, password }),\n          });\n\n          let data = {};\n          try {\n            data = await res.json();\n          } catch (_) {\n            const txt = await res.text().catch(() => '');\n            data = { message: txt, error: txt };\n          }\n\n          if (res.ok) {\n            if (data.token) {\n              localStorage.setItem('bowtie_auth_token', data.token);\n              this.authToken = data.token;\n            }\n            if (data.user) this.currentUser = data.user;\n            clearMessages();\n            modal.classList.remove('show');\n            await this.loadInitialData();\n            if (this.canvas) this.canvas.render();\n            if (this.tree) this.tree.render();\n            if (this.documents) this.documents.render();\n            this.showNotification(`Signed in securely as ${this.currentUser.name || this.currentUser.email}!`, 'success');\n          } else {\n            const errMsg = data.message || data.error || 'Sign in failed.';\n            if (res.status === 404) {\n              if (confirm('No account found for this email. Would you like to create one now?')) {\n                showSignupTab();\n                const signupEmail = document.getElementById('authSignupEmail');\n                if (signupEmail) signupEmail.value = email;\n              }\n            } else {\n              if (errBox) {\n                errBox.textContent = errMsg;\n                errBox.style.display = 'block';\n              } else {\n                alert('Sign In Failed: ' + errMsg);\n              }\n            }\n          }\n        } catch (err) {\n          if (errBox) {\n            errBox.textContent = 'Sign In Network Error: ' + err.message;\n            errBox.style.display = 'block';\n          } else {\n            alert('Sign In Network Error: ' + err.message);\n          }\n        }\n      };\n    }\n\n    // Handle Signup Form\n    if (formSignup) {\n      formSignup.onsubmit = async (e) => {\n        e.preventDefault();\n        const name = document.getElementById('authSignupName').value.trim();\n        const email = document.getElementById('authSignupEmail').value.trim();\n        const password = document.getElementById('authSignupPassword').value;\n        const organization = document.getElementById('authSignupOrg').value.trim();\n        const role = document.getElementById('authSignupRole').value.trim();\n        clearMessages();\n\n        try {\n          const res = await fetch('/api/auth/signup', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({\n              name,\n              email,\n              password,\n              organization: organization || undefined,\n              role: role || 'Risk Assessor',\n            }),\n          });\n\n          let data = {};\n          try {\n            data = await res.json();\n          } catch (_) {\n            const txt = await res.text().catch(() => '');\n            data = { message: txt, error: txt };\n          }\n\n          if (res.ok) {\n            if (data.token) {\n              localStorage.setItem('bowtie_auth_token', data.token);\n              this.authToken = data.token;\n            }\n            if (data.user) this.currentUser = data.user;\n            clearMessages();\n            modal.classList.remove('show');\n            await this.loadInitialData();\n            if (this.canvas) this.canvas.render();\n            if (this.tree) this.tree.render();\n            if (this.documents) this.documents.render();\n            this.showNotification(`Account created! Welcome, ${this.currentUser.name || this.currentUser.email}!`, 'success');\n          } else {\n            const errMsg = data.message || data.error || 'Signup failed.';\n            if (errBox) {\n              errBox.textContent = errMsg;\n              errBox.style.display = 'block';\n            } else {\n              alert('Signup Failed: ' + errMsg);\n            }\n          }\n        } catch (err) {\n          if (errBox) {\n            errBox.textContent = 'Signup Network Error: ' + err.message;\n            errBox.style.display = 'block';\n          } else {\n            alert('Signup Network Error: ' + err.message);\n          }\n        }\n      };\n    }\n\n    // Handle Modal Forgot Password Form (Step 1)\n    if (formForgot) {\n      formForgot.onsubmit = async (e) => {\n        e.preventDefault();\n        const email = (document.getElementById('modalForgotEmail')?.value || '').trim();\n        const btn = document.getElementById('btnModalSubmitForgot');\n        clearMessages();\n\n        if (!email) {\n          if (errBox) { errBox.textContent = 'Please enter your account email.'; errBox.style.display = 'block'; }\n          return;\n        }\n\n        if (btn) btn.disabled = true;\n\n        try {\n          const res = await fetch('/api/auth/forgot-password', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ email })\n          });\n          const data = await res.json().catch(() => ({}));\n\n          if (res.ok) {\n            showResetTab(email, data.message || `Verification code generated for ${email}. Check below.`);\n            const codeInput = document.getElementById('modalResetCode');\n            if (codeInput && data.code) {\n              codeInput.value = data.code;\n            }\n          } else {\n            if (errBox) {\n              errBox.textContent = data.message || data.error || 'Failed to request verification code.';\n              errBox.style.display = 'block';\n            }\n          }\n        } catch (err) {\n          if (errBox) {\n            errBox.textContent = 'Network error while requesting password reset.';\n            errBox.style.display = 'block';\n          }\n        } finally {\n          if (btn) btn.disabled = false;\n        }\n      };\n    }\n\n    // Handle Modal Reset Password Form (Step 2)\n    if (formReset) {\n      formReset.onsubmit = async (e) => {\n        e.preventDefault();\n        const email = (document.getElementById('modalResetEmail')?.value || '').trim();\n        const code = (document.getElementById('modalResetCode')?.value || '').trim();\n        const new_password = document.getElementById('modalResetNewPassword')?.value || '';\n        const confirm_password = document.getElementById('modalResetConfirmPassword')?.value || '';\n        const btn = document.getElementById('btnModalSubmitReset');\n        clearMessages();\n\n        if (!code || code.length !== 6) {\n          if (errBox) { errBox.textContent = 'Please enter the 6-digit verification code.'; errBox.style.display = 'block'; }\n          return;\n        }\n        if (new_password.length < 4) {\n          if (errBox) { errBox.textContent = 'New password must be at least 4 characters.'; errBox.style.display = 'block'; }\n          return;\n        }\n        if (new_password !== confirm_password) {\n          if (errBox) { errBox.textContent = 'Passwords do not match. Please re-enter.'; errBox.style.display = 'block'; }\n          return;\n        }\n\n        if (btn) btn.disabled = true;\n\n        try {\n          const res = await fetch('/api/auth/reset-password', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ email, code, new_password })\n          });\n          const data = await res.json().catch(() => ({}));\n\n          if (res.ok) {\n            if (data.token) {\n              localStorage.setItem('bowtie_auth_token', data.token);\n              this.authToken = data.token;\n            }\n            this.currentUser = data.user;\n            this.updateUserUI();\n            this.initRealtimeCollaboration();\n\n            const wsRes = await this.apiFetch('/api/workspace');\n            if (wsRes.ok) this.workspace = await wsRes.json();\n\n            this.canvas.render();\n            this.tree.render();\n            if (this.documents) this.documents.render();\n            modal.classList.remove('show');\n            this.showNotification(`Password reset successfully! Welcome back, ${this.currentUser.name || email}.`, 'success');\n          } else {\n            if (errBox) {\n              errBox.textContent = data.message || data.error || 'Password reset failed.';\n              errBox.style.display = 'block';\n            }\n          }\n        } catch (err) {\n          if (errBox) {\n            errBox.textContent = 'Network error while resetting password.';\n            errBox.style.display = 'block';\n          }\n        } finally {\n          if (btn) btn.disabled = false;\n        }\n      };\n    }\n\n    modal.classList.add('show');\n  }\n\n  openMobileDrawer(side) {\n    const leftPane = document.getElementById('leftPane');\n    const rightPane = document.getElementById('rightPane');\n    const mobileBackdrop = document.getElementById('mobileDrawerBackdrop');\n    const btnCollapseLeft = document.getElementById('btnCollapseLeft');\n    const btnCollapseRight = document.getElementById('btnCollapseRight');\n\n    this._drawerOpenedTime = Date.now();\n    this.closeMobileDrawers();\n\n    if (side === 'left' && leftPane) {\n      leftPane.classList.remove('collapsed');\n      leftPane.classList.add('drawer-open');\n      if (btnCollapseLeft) btnCollapseLeft.textContent = '✕';\n      if (mobileBackdrop) mobileBackdrop.classList.add('show');\n    } else if (side === 'right' && rightPane) {\n      rightPane.classList.remove('collapsed');\n      rightPane.classList.add('drawer-open');\n      if (btnCollapseRight) btnCollapseRight.textContent = '✕';\n      if (mobileBackdrop) mobileBackdrop.classList.add('show');\n    }\n  }\n\n  closeMobileDrawers() {\n    const leftPane = document.getElementById('leftPane');\n    const rightPane = document.getElementById('rightPane');\n    const mobileBackdrop = document.getElementById('mobileDrawerBackdrop');\n    const btnCollapseLeft = document.getElementById('btnCollapseLeft');\n    const btnCollapseRight = document.getElementById('btnCollapseRight');\n\n    if (leftPane) leftPane.classList.remove('drawer-open');\n    if (rightPane) rightPane.classList.remove('drawer-open');\n    if (mobileBackdrop) mobileBackdrop.classList.remove('show');\n    this.updateSidebarStateUI();\n  }\n\n  escapeHtml(text) {\n    if (!text) return '';\n    return String(text)\n      .replace(/&/g, '&amp;')\n      .replace(/</g, '&lt;')\n      .replace(/>/g, '&gt;')\n      .replace(/\"/g, '&quot;')\n      .replace(/'/g, '&#039;');\n  }\n\n  showNotification(msg, type = 'info') {\n    let toast = document.getElementById('appNotificationToast');\n    if (!toast) {\n      toast = document.createElement('div');\n      toast.id = 'appNotificationToast';\n      toast.style.cssText = `\n        position: fixed;\n        bottom: 24px;\n        right: 24px;\n        background-color: #0f172a;\n        color: #ffffff;\n        padding: 10px 18px;\n        border-radius: 8px;\n        box-shadow: 0 10px 25px rgba(0,0,0,0.25);\n        font-size: 13px;\n        z-index: 9999;\n        display: flex;\n        align-items: center;\n        gap: 8px;\n        transition: opacity 0.25s, transform 0.25s;\n        opacity: 0;\n        transform: translateY(12px);\n      `;\n      document.body.appendChild(toast);\n    }\n\n    const dotColor = type === 'success' ? '#10b981' : (type === 'danger' ? '#ef4444' : '#3b82f6');\n    toast.innerHTML = `<span style=\"width:8px; height:8px; border-radius:50%; background:${dotColor};\"></span><span>${msg}</span>`;\n\n    toast.style.opacity = '1';\n    toast.style.transform = 'translateY(0)';\n\n    if (this._toastTimer) clearTimeout(this._toastTimer);\n    this._toastTimer = setTimeout(() => {\n      toast.style.opacity = '0';\n      toast.style.transform = 'translateY(12px)';\n    }, 3200);\n  }\n\n  // ==========================================================================\n  // REAL-TIME MULTI-USER COLLABORATION ENGINE (Google Docs Style)\n  // ==========================================================================\n  initRealtimeCollaboration() {\n    if (this.currentView && this.currentView !== 'canvas') return;\n    if (!this.currentDiagram) return;\n\n    if (this.realtimeSource) {\n      this.realtimeSource.close();\n      this.realtimeSource = null;\n    }\n\n    if (this.canvas) {\n      this.canvas.clearRemotePresences();\n    }\n    this.collaborators.clear();\n\n    const diagramId = this.currentDiagram.id;\n    const userId = this.currentUser ? this.currentUser.id : 'guest';\n    const userName = this.currentUser ? (this.currentUser.name || this.currentUser.email.split('@')[0]) : 'Guest Collaborator';\n    const userEmail = this.currentUser ? this.currentUser.email : '';\n    const userColor = this.userColor;\n\n    // Immediately update active users stack so the user sees their own presence right away\n    this.updateCollaboratorsStack();\n\n    const streamUrl = `/api/realtime/stream?diagram_id=${encodeURIComponent(diagramId)}&session_id=${encodeURIComponent(this.sessionId)}&user_id=${encodeURIComponent(userId)}&user_name=${encodeURIComponent(userName)}&user_email=${encodeURIComponent(userEmail)}&user_color=${encodeURIComponent(userColor)}&token=${encodeURIComponent(this.authToken || '')}`;\n\n    try {\n      this.realtimeSource = new EventSource(streamUrl);\n\n      this.realtimeSource.onmessage = (e) => {\n        if (!e.data) return;\n        try {\n          const msg = JSON.parse(e.data);\n          this.handleRealtimeEvent(msg);\n        } catch (err) {\n          // heartbeat or non-json message\n        }\n      };\n\n      this.realtimeSource.onerror = () => {\n        // EventSource automatically reconnects on error\n      };\n    } catch (err) {\n      console.warn('Real-time connection error:', err);\n    }\n  }\n\n  disconnectRealtimeCollaboration() {\n    if (this.realtimeSource) {\n      this.realtimeSource.close();\n      this.realtimeSource = null;\n    }\n    if (this.currentDiagram) {\n      this.leaveCurrentDiagramPresence(this.currentDiagram.id);\n    }\n    if (this.canvas) {\n      this.canvas.clearRemotePresences();\n    }\n    this.collaborators.clear();\n    this.updateCollaboratorsStack();\n  }\n\n  handleRealtimeEvent(msg) {\n    if (!msg || !msg.type) return;\n\n    if (msg.type === 'presence_list') {\n      const list = msg.data || [];\n      this.collaborators.clear();\n      if (this.canvas) {\n        this.canvas.clearRemotePresences();\n      }\n      list.forEach(p => {\n        const isSelfSession = (p.session_id === this.sessionId);\n        const isSelfUser = (this.currentUser && p.user_id && p.user_id === this.currentUser.id);\n        const isSelfEmail = (this.currentUser && p.user_email && p.user_email.toLowerCase() === this.currentUser.email.toLowerCase());\n        if (!isSelfSession && !isSelfUser && !isSelfEmail) {\n          this.collaborators.set(p.session_id, p);\n          if (p.cursor_x != null && p.cursor_y != null && this.canvas) {\n            this.canvas.updateRemoteCursor(p.session_id, p.user_name, p.user_color, p.cursor_x, p.cursor_y);\n          }\n          if (p.active_node_id && this.canvas) {\n            this.canvas.updateRemoteNodeHighlight(p.session_id, p.user_name, p.user_color, p.active_node_id);\n          }\n        }\n      });\n      this.updateCollaboratorsStack();\n    } else if (msg.type === 'user_joined') {\n      const p = msg.data;\n      const isSelfSession = (p && p.session_id === this.sessionId);\n      const isSelfUser = (this.currentUser && p && p.user_id && p.user_id === this.currentUser.id);\n      const isSelfEmail = (this.currentUser && p && p.user_email && p.user_email.toLowerCase() === this.currentUser.email.toLowerCase());\n      if (p && !isSelfSession && !isSelfUser && !isSelfEmail) {\n        this.collaborators.set(p.session_id, p);\n        this.updateCollaboratorsStack();\n        this.showNotification(`${p.user_name} joined the diagram`, 'info');\n      }\n    } else if (msg.type === 'user_left') {\n      const p = msg.data;\n      if (p && p.session_id) {\n        this.collaborators.delete(p.session_id);\n        this.updateCollaboratorsStack();\n        if (this.canvas) {\n          this.canvas.removeRemoteCursor(p.session_id);\n          this.canvas.removeRemoteNodeHighlight(p.session_id);\n        }\n      }\n    } else if (msg.type === 'cursor_move') {\n      const c = msg.data;\n      const isSelfSession = (c && c.session_id === this.sessionId);\n      const isSelfUser = (this.currentUser && c && c.user_id && c.user_id === this.currentUser.id);\n      if (c && !isSelfSession && !isSelfUser && this.canvas) {\n        this.canvas.updateRemoteCursor(c.session_id, c.user_name, c.user_color, c.x, c.y);\n      }\n    } else if (msg.type === 'node_selected') {\n      const n = msg.data;\n      const isSelfSession = (n && n.session_id === this.sessionId);\n      const isSelfUser = (this.currentUser && n && n.user_id && n.user_id === this.currentUser.id);\n      if (n && !isSelfSession && !isSelfUser && this.canvas) {\n        this.canvas.updateRemoteNodeHighlight(n.session_id, n.user_name, n.user_color, n.node_id);\n      }\n    } else if (msg.type === 'diagram_updated') {\n      const d = msg.data;\n      if (d && d.diagram_id === (this.currentDiagram && this.currentDiagram.id)) {\n        if (d.session_id === this.sessionId) return; // Don't echo self\n        if (!d.session_id && this.currentUser && d.updated_by && d.updated_by.toLowerCase() === this.currentUser.email.toLowerCase() && (this.isSaving || this.autoSaveTimer)) return;\n\n        if (d.nodes && Array.isArray(d.nodes)) {\n          // Immediately update diagram nodes in memory for zero-latency sync\n          const otherNodes = (this.workspace.nodes || []).filter(n => n.diagram_id !== d.diagram_id);\n          this.workspace.nodes = otherNodes.concat(d.nodes);\n          this.normalizeNodeDimensions();\n          this.fixInvertedOutcomeBarriers();\n\n          // Re-render canvas if user is not actively dragging a node\n          if (!this.canvas || !this.canvas.isDraggingNode) {\n            if (this.canvas) this.canvas.render();\n            if (this.tree) this.tree.render();\n          }\n\n          if (d.updated_by) {\n            const updaterName = d.updated_by.split('@')[0];\n            this.setSaveStatusText(`Updated by ${updaterName}`, 'saved');\n          }\n        } else {\n          this.refreshWorkspaceSilently();\n        }\n      }\n    } else if (msg.type === 'sim_action') {\n      const s = msg.data;\n      if (s && s.diagram_id === (this.currentDiagram && this.currentDiagram.id)) {\n        if (s.session_id !== this.sessionId && this.situationRunner) {\n          this.situationRunner.handleRemoteAction(s);\n        }\n      }\n    }\n  }\n\n  sendPresenceUpdate(canvasX, canvasY, activeNodeId) {\n    const now = Date.now();\n    if (now - this.lastPresenceSent < 60) return; // 60ms throttle\n    this.lastPresenceSent = now;\n\n    if (!this.currentDiagram) return;\n\n    this.apiFetch('/api/realtime/presence', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        diagram_id: this.currentDiagram.id,\n        session_id: this.sessionId,\n        x: canvasX != null ? canvasX : undefined,\n        y: canvasY != null ? canvasY : undefined,\n        active_node_id: activeNodeId || null,\n      }),\n    }).catch(() => {});\n  }\n\n  updateCollaboratorsStack() {\n    const listEl = document.getElementById('collabAvatarsList');\n    if (!listEl) return;\n\n    listEl.innerHTML = '';\n\n    // If not in canvas view or no diagram active, do not display active people on diagram\n    if ((this.currentView && this.currentView !== 'canvas') || !this.currentDiagram) {\n      return;\n    }\n\n    // 1. Always render self avatar chip\n    const selfUser = this.currentUser || { email: 'guest@bowtierisk.com', name: 'You' };\n    const selfChip = document.createElement('div');\n    selfChip.className = 'collab-avatar-chip self';\n    selfChip.style.borderColor = this.userColor || '#2563eb';\n    selfChip.style.overflow = 'hidden';\n    selfChip.style.padding = '0';\n    selfChip.title = `You (${selfUser.name || selfUser.email}) - Active now on this diagram`;\n    selfChip.innerHTML = this.getUserAvatarHtml(selfUser, 24);\n    listEl.appendChild(selfChip);\n\n    // 2. Render all remote collaborator chips\n    this.collaborators.forEach(p => {\n      const u = this.getUserByEmail(p.user_email) || { email: p.user_email, name: p.user_name, avatar_color: p.user_color };\n      const chip = document.createElement('div');\n      chip.className = 'collab-avatar-chip';\n      chip.style.borderColor = p.user_color || '#10b981';\n      chip.style.overflow = 'hidden';\n      chip.style.padding = '0';\n      chip.title = `${p.user_name} (${p.user_email || 'Active'}) - Active now (Click to locate)`;\n      chip.innerHTML = this.getUserAvatarHtml(u, 24);\n      chip.onclick = () => {\n        if (p.cursor_x != null && p.cursor_y != null && this.canvas) {\n          this.canvas.centerOnCoordinates(p.cursor_x, p.cursor_y);\n        } else if (p.active_node_id && this.canvas) {\n          this.canvas.centerOnNode(p.active_node_id);\n          this.canvas.setActiveNode(p.active_node_id);\n        }\n      };\n      listEl.appendChild(chip);\n    });\n\n    // 3. Active count pill\n    const count = this.collaborators.size + 1;\n    const countTag = document.createElement('span');\n    countTag.className = 'collab-self-tag';\n    countTag.title = `${count} active user${count === 1 ? '' : 's'} on this diagram`;\n    countTag.textContent = `${count} active`;\n    listEl.appendChild(countTag);\n  }\n\n  async refreshWorkspaceSilently() {\n    try {\n      const res = await this.apiFetch('/api/workspace');\n      if (res.ok) {\n        this.workspace = await res.json();\n        this.normalizeNodeDimensions();\n        this.fixInvertedOutcomeBarriers();\n        if (this.canvas) this.canvas.render();\n        if (this.tree) this.tree.render();\n        this.showNotification('Diagram synchronized with team changes', 'info');\n      }\n    } catch (e) {}\n  }\n\n  // ==========================================================================\n  // ENTERPRISE ADMIN PORTAL & INSTANCE MANAGEMENT\n  // ==========================================================================\n  openAdminPortal() {\n    this.openAdminTab();\n  }\n\n  openAdminTab() {\n    if (!this.currentUser || !this.currentUser.is_admin) {\n      this.showNotification('Administrator privileges required to access the Admin Portal.', 'error');\n      return;\n    }\n    if (!this.openDiagramIds.includes('admin')) {\n      this.openDiagramIds.push('admin');\n      this.saveOpenDiagramIds();\n    }\n    this.switchToView('admin');\n  }\n\n  switchAdminTab(tabName) {\n    const tabUsersBtn = document.getElementById('tabAdminUsersBtn');\n    const tabConfigBtn = document.getElementById('tabAdminConfigBtn');\n    const tabSuggestionsBtn = document.getElementById('tabAdminSuggestionsBtn');\n    const tabLicenseBtn = document.getElementById('tabAdminLicenseBtn');\n    const usersPane = document.getElementById('adminUsersPane');\n    const configPane = document.getElementById('adminConfigPane');\n    const suggestionsPane = document.getElementById('adminSuggestionsPane');\n    const licensePane = document.getElementById('adminLicensePane');\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    if (tabUsersBtn) tabUsersBtn.classList.toggle('active', tabName === 'users');\n    if (tabConfigBtn) tabConfigBtn.classList.toggle('active', tabName === 'config');\n    if (tabSuggestionsBtn) tabSuggestionsBtn.classList.toggle('active', tabName === 'suggestions');\n    if (tabLicenseBtn) tabLicenseBtn.classList.toggle('active', tabName === 'license');\n\n    if (usersPane) usersPane.style.display = tabName === 'users' ? 'block' : 'none';\n    if (configPane) configPane.style.display = tabName === 'config' ? 'block' : 'none';\n    if (suggestionsPane) suggestionsPane.style.display = tabName === 'suggestions' ? 'block' : 'none';\n    if (licensePane) licensePane.style.display = tabName === 'license' ? 'block' : 'none';\n\n    if (tabName === 'suggestions') {\n      this.renderAdminSuggestions();\n    } else if (tabName === 'license') {\n      this.loadAdminLicenseAndUpdateStatus();\n    }\n  }\n\n  async loadAdminUsers() {\n    const alertEl = document.getElementById('adminPortalAlert');\n    const countBadge = document.getElementById('adminUserCountBadge');\n    try {\n      const res = await this.apiFetch('/api/admin/users');\n      if (!res.ok) {\n        const err = await res.json().catch(() => ({}));\n        throw new Error(err.error || 'Failed to load user directory.');\n      }\n      this.adminUsersList = await res.json();\n      if (countBadge) {\n        countBadge.textContent = `${this.adminUsersList.length} registered user${this.adminUsersList.length === 1 ? '' : 's'}`;\n      }\n      this.renderAdminUsersTable();\n    } catch (e) {\n      console.error(e);\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n    }\n  }\n\n  renderAdminUsersTable() {\n    const tbody = document.getElementById('adminUsersTableBody');\n    if (!tbody) return;\n    const searchVal = (document.getElementById('adminUserSearchInput')?.value || '').toLowerCase().trim();\n\n    const filtered = (this.adminUsersList || []).filter(u => {\n      if (!searchVal) return true;\n      return (\n        (u.name && u.name.toLowerCase().includes(searchVal)) ||\n        (u.email && u.email.toLowerCase().includes(searchVal)) ||\n        (u.role && u.role.toLowerCase().includes(searchVal)) ||\n        (u.organization && u.organization.toLowerCase().includes(searchVal))\n      );\n    });\n\n    if (filtered.length === 0) {\n      tbody.innerHTML = `<tr><td colspan=\"6\" class=\"text-center text-muted\" style=\"padding: 24px;\">No users found matching your search.</td></tr>`;\n      return;\n    }\n\n    tbody.innerHTML = filtered.map(u => {\n      const isMe = this.currentUser && this.currentUser.id === u.id;\n      const roleBadge = u.is_admin\n        ? `<span class=\"badge-role-admin\"><svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 14h-2v-2h2zm0-4h-2V7h2z\"/></svg> Administrator</span>`\n        : `<span class=\"badge-role-user\">Standard User</span>`;\n\n      const statusBadge = u.is_disabled\n        ? `<span class=\"badge-status-disabled\">Blocked / Disabled</span>`\n        : `<span class=\"badge-status-active\">Active</span>`;\n\n      return `\n        <tr class=\"admin-user-row-clickable\">\n          <td onclick=\"window.app.openAdminEditUserModal('${u.id}')\" title=\"Click to view and edit profile\" style=\"cursor: pointer;\">\n            <div class=\"flex items-center gap-2\">\n              ${this.getUserAvatarHtml(u, 32)}\n              <div>\n                <div style=\"font-weight: 600; color: #0f172a;\">\n                  ${this.escapeHtml(u.name || 'Unnamed Assessor')} ${isMe ? '<span style=\"font-size: 10px; color: #2563eb; font-weight: normal;\">(You)</span>' : ''}\n                </div>\n                <div class=\"text-xs text-muted\" style=\"font-family: monospace;\">${this.escapeHtml(u.email)}</div>\n              </div>\n            </div>\n          </td>\n          <td onclick=\"window.app.openAdminEditUserModal('${u.id}')\" title=\"Click to view and edit profile\" style=\"cursor: pointer;\">\n            <div class=\"text-xs\" style=\"color: #334155; font-weight: 500;\">${this.escapeHtml(u.role || 'Risk Assessor')}</div>\n            <div class=\"text-xs text-muted\">${this.escapeHtml(u.organization || 'General')}</div>\n          </td>\n          <td>\n            <div class=\"flex items-center gap-2\">\n              ${roleBadge}\n              <button type=\"button\" class=\"btn btn-outline btn-sm\" style=\"font-size: 11px; padding: 2px 7px;\" \n                onclick=\"event.stopPropagation(); window.app.toggleUserAdminRole('${u.id}', ${!u.is_admin})\" title=\"${u.is_admin ? 'Demote to regular user' : 'Promote to administrator'}\">\n                ${u.is_admin ? 'Demote' : 'Make Admin'}\n              </button>\n            </div>\n          </td>\n          <td>\n            <div class=\"flex items-center gap-2\">\n              ${statusBadge}\n              <button type=\"button\" class=\"btn btn-outline btn-sm\" style=\"font-size: 11px; padding: 2px 7px; color: ${u.is_disabled ? '#16a34a' : '#dc2626'}; border-color: ${u.is_disabled ? '#86efac' : '#fca5a5'};\" \n                onclick=\"event.stopPropagation(); window.app.toggleUserDisabledStatus('${u.id}', ${!u.is_disabled})\" ${isMe ? 'disabled title=\"Cannot disable your own active account\"' : ''}>\n                ${u.is_disabled ? 'Enable' : 'Disable'}\n              </button>\n            </div>\n          </td>\n          <td class=\"text-center\" onclick=\"window.app.openAdminEditUserModal('${u.id}')\" style=\"cursor: pointer;\">\n            <span class=\"badge\" style=\"background: ${(u.active_sessions_count || 0) > 0 ? '#eff6ff' : '#f8fafc'}; color: ${(u.active_sessions_count || 0) > 0 ? '#1d4ed8' : '#64748b'};\">\n              ${u.active_sessions_count || 0}\n            </span>\n          </td>\n          <td>\n            <div class=\"flex items-center gap-1\">\n              <button type=\"button\" class=\"btn btn-outline btn-sm\" style=\"font-size: 11px; padding: 3px 8px; color: #7c3aed; border-color: #ddd6fe;\"\n                onclick=\"window.app.openAdminEditUserModal('${u.id}')\" title=\"Edit complete user profile and settings\">\n                Edit\n              </button>\n              <button type=\"button\" class=\"btn btn-outline btn-sm\" style=\"font-size: 11px; padding: 3px 8px;\"\n                onclick=\"event.stopPropagation(); window.app.openAdminResetPasswordModal('${u.id}', '${this.escapeHtml(u.email)}')\">\n                Reset PW\n              </button>\n            </div>\n          </td>\n        </tr>\n      `;\n    }).join('');\n  }\n\n  async toggleUserAdminRole(userId, makeAdmin) {\n    const action = makeAdmin ? 'promote this user to Administrator' : 'demote this user to Standard User';\n    if (!confirm(`Are you sure you want to ${action}?`)) return;\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    try {\n      const res = await this.apiFetch(`/api/admin/users/${userId}/role`, {\n        method: 'PUT',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ is_admin: makeAdmin })\n      });\n      const data = await res.json();\n      if (!res.ok) {\n        throw new Error(data.error || 'Failed to update user role.');\n      }\n      this.showNotification(`User role updated to ${makeAdmin ? 'Administrator' : 'Standard User'}.`, 'success');\n      if (noticeEl) {\n        noticeEl.textContent = `User role updated successfully.`;\n        noticeEl.style.display = 'block';\n      }\n      await this.loadAdminUsers();\n      if (this.currentUser && this.currentUser.id === userId) {\n        this.currentUser.is_admin = makeAdmin;\n        this.updateUserUI();\n      }\n    } catch (e) {\n      console.error(e);\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n      this.showNotification(e.message, 'error');\n    }\n  }\n\n  async toggleUserDisabledStatus(userId, makeDisabled) {\n    const action = makeDisabled ? 'block/disable this account (all active sessions will be terminated)' : 're-enable this account';\n    if (!confirm(`Are you sure you want to ${action}?`)) return;\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    try {\n      const res = await this.apiFetch(`/api/admin/users/${userId}/status`, {\n        method: 'PUT',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ is_disabled: makeDisabled })\n      });\n      const data = await res.json();\n      if (!res.ok) {\n        throw new Error(data.error || 'Failed to update account status.');\n      }\n      this.showNotification(`Account ${makeDisabled ? 'disabled & sessions revoked' : 'activated'} successfully.`, 'success');\n      if (noticeEl) {\n        noticeEl.textContent = `Account status updated successfully.`;\n        noticeEl.style.display = 'block';\n      }\n      await this.loadAdminUsers();\n    } catch (e) {\n      console.error(e);\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n      this.showNotification(e.message, 'error');\n    }\n  }\n\n  openAdminResetPasswordModal(userId, email) {\n    const modal = document.getElementById('modalAdminResetPassword');\n    const targetIdInput = document.getElementById('adminResetTargetUserId');\n    const targetEmailLabel = document.getElementById('adminResetTargetEmail');\n    const newPassInput = document.getElementById('adminResetNewPassword');\n    const confirmPassInput = document.getElementById('adminResetConfirmPassword');\n    const alertEl = document.getElementById('adminResetPasswordAlert');\n\n    if (alertEl) alertEl.style.display = 'none';\n    if (targetIdInput) targetIdInput.value = userId;\n    if (targetEmailLabel) targetEmailLabel.textContent = email;\n    if (newPassInput) newPassInput.value = '';\n    if (confirmPassInput) confirmPassInput.value = '';\n\n    if (modal) modal.classList.add('show');\n  }\n\n  async submitAdminResetPassword(e) {\n    if (e) e.preventDefault();\n    const targetId = document.getElementById('adminResetTargetUserId')?.value;\n    const newPassword = document.getElementById('adminResetNewPassword')?.value || '';\n    const confirmPassword = document.getElementById('adminResetConfirmPassword')?.value || '';\n    const alertEl = document.getElementById('adminResetPasswordAlert');\n\n    if (!newPassword || newPassword.length < 4) {\n      if (alertEl) {\n        alertEl.textContent = 'Password must be at least 4 characters.';\n        alertEl.style.display = 'block';\n      }\n      return;\n    }\n    if (newPassword !== confirmPassword) {\n      if (alertEl) {\n        alertEl.textContent = 'Passwords do not match.';\n        alertEl.style.display = 'block';\n      }\n      return;\n    }\n\n    try {\n      const res = await this.apiFetch(`/api/admin/users/${targetId}/reset-password`, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ new_password: newPassword })\n      });\n      const data = await res.json();\n      if (!res.ok) {\n        throw new Error(data.error || 'Failed to reset password.');\n      }\n      this.showNotification(data.message || 'Password reset successfully.', 'success');\n      const modal = document.getElementById('modalAdminResetPassword');\n      if (modal) modal.classList.remove('show');\n      await this.loadAdminUsers();\n    } catch (e) {\n      console.error(e);\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n    }\n  }\n\n  compressImageFile(file, maxWidth = 256, maxHeight = 256, quality = 0.85) {\n    return new Promise((resolve) => {\n      if (!file) return resolve(null);\n      if (file.type === 'image/svg+xml') {\n        const reader = new FileReader();\n        reader.onload = (e) => resolve(e.target.result);\n        reader.onerror = () => resolve(null);\n        reader.readAsDataURL(file);\n        return;\n      }\n      const reader = new FileReader();\n      reader.onload = (e) => {\n        const img = new Image();\n        img.onload = () => {\n          try {\n            const canvas = document.createElement('canvas');\n            let w = img.width;\n            let h = img.height;\n            let sx = 0, sy = 0, sSize = Math.min(w, h);\n            if (w > h) {\n              sx = (w - h) / 2;\n            } else if (h > w) {\n              sy = (h - w) / 2;\n            }\n            const outSize = Math.min(maxWidth, sSize, 256);\n            canvas.width = outSize;\n            canvas.height = outSize;\n            const ctx = canvas.getContext('2d');\n            ctx.drawImage(img, sx, sy, sSize, sSize, 0, 0, outSize, outSize);\n            const dataUrl = canvas.toDataURL('image/jpeg', quality);\n            resolve(dataUrl);\n          } catch (err) {\n            resolve(e.target.result);\n          }\n        };\n        img.onerror = () => resolve(e.target.result);\n        img.src = e.target.result;\n      };\n      reader.onerror = () => resolve(null);\n      reader.readAsDataURL(file);\n    });\n  }\n\n  // ==========================================================================\n  // USER PROFILE & AVATAR MANAGEMENT (REQUIREMENT 1)\n  // ==========================================================================\n  openMyProfileModal() {\n    const modal = document.getElementById('modalMyProfile');\n    if (!modal) return;\n    const alertEl = document.getElementById('myProfileAlert');\n    const noticeEl = document.getElementById('myProfileNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    this.profileEditAvatarValue = this.currentUser ? this.currentUser.avatar_url : null;\n\n    const nameInput = document.getElementById('myProfileNameInput');\n    const emailDisplay = document.getElementById('myProfileEmailDisplay');\n    const roleInput = document.getElementById('myProfileRoleInput');\n    const orgInput = document.getElementById('myProfileOrgInput');\n    const phoneInput = document.getElementById('myProfilePhoneInput');\n\n    if (nameInput) nameInput.value = (this.currentUser && this.currentUser.name) || '';\n    if (emailDisplay) emailDisplay.value = (this.currentUser && this.currentUser.email) || '';\n    if (roleInput) roleInput.value = (this.currentUser && this.currentUser.role) || '';\n    if (orgInput) orgInput.value = (this.currentUser && this.currentUser.organization) || '';\n    if (phoneInput) phoneInput.value = (this.currentUser && this.currentUser.phone) || '';\n\n    this.renderMyProfileAvatarPreview();\n    this.renderAvatarPresets('myProfilePresetIcons', (icon) => {\n      this.profileEditAvatarValue = icon;\n      this.renderMyProfileAvatarPreview();\n    });\n\n    modal.classList.add('show');\n  }\n\n  renderMyProfileAvatarPreview() {\n    const preview = document.getElementById('myProfileAvatarPreview');\n    if (!preview) return;\n    const tempUser = {\n      ...(this.currentUser || {}),\n      avatar_url: this.profileEditAvatarValue\n    };\n    preview.innerHTML = this.getUserAvatarHtml(tempUser, 72);\n  }\n\n  renderAvatarPresets(containerId, onSelect) {\n    const container = document.getElementById(containerId);\n    if (!container) return;\n    const icons = ['👤', '👷', '🛡️', '⚙️', '🔬', '⚡', '🏢', '⚓', '🚒', '🎯', '🚀', '💻', '🧠', '📊'];\n    container.innerHTML = icons.map(icon => `\n      <button type=\"button\" class=\"avatar-preset-chip\" title=\"Select icon\">${icon}</button>\n    `).join('');\n    container.querySelectorAll('.avatar-preset-chip').forEach((btn, idx) => {\n      btn.addEventListener('click', () => {\n        container.querySelectorAll('.avatar-preset-chip').forEach(b => b.classList.remove('active'));\n        btn.classList.add('active');\n        onSelect(icons[idx]);\n      });\n    });\n  }\n\n  async saveMyProfile(e) {\n    if (e && e.preventDefault) e.preventDefault();\n    const alertEl = document.getElementById('myProfileAlert');\n    const noticeEl = document.getElementById('myProfileNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    const name = document.getElementById('myProfileNameInput')?.value?.trim();\n    const role = document.getElementById('myProfileRoleInput')?.value?.trim();\n    const org = document.getElementById('myProfileOrgInput')?.value?.trim();\n    const phone = document.getElementById('myProfilePhoneInput')?.value?.trim();\n\n    if (!name) {\n      if (alertEl) {\n        alertEl.textContent = 'Please provide your full name.';\n        alertEl.style.display = 'block';\n      }\n      return;\n    }\n\n    try {\n      const res = await this.apiFetch('/api/auth/profile', {\n        method: 'PUT',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          name,\n          role: role || null,\n          organization: org || null,\n          phone: phone || null,\n          avatar_url: this.profileEditAvatarValue || null\n        })\n      });\n\n      let updatedUser;\n      try {\n        updatedUser = await res.json();\n      } catch (jsonErr) {\n        const errText = await res.text().catch(() => '');\n        throw new Error(errText || `Server returned error status ${res.status}`);\n      }\n\n      if (!res.ok) {\n        throw new Error((updatedUser && updatedUser.error) || 'Failed to update profile.');\n      }\n\n      this.currentUser = updatedUser;\n      try {\n        localStorage.setItem('bowtierisk_user', JSON.stringify(updatedUser));\n      } catch (err) {}\n\n      this.updateUserUI();\n      if (this.directoryUsersMap) {\n        this.directoryUsersMap[updatedUser.id] = updatedUser;\n      }\n\n      this.showNotification('Profile updated successfully!', 'success');\n      const modal = document.getElementById('modalMyProfile');\n      if (modal) modal.classList.remove('show');\n    } catch (err) {\n      console.error(err);\n      if (alertEl) {\n        alertEl.textContent = err.message || 'Failed to update profile.';\n        alertEl.style.display = 'block';\n      }\n    }\n  }\n\n  // ==========================================================================\n  // ADMIN USER EDIT & DELETION (REQUIREMENT 2)\n  // ==========================================================================\n  openAdminEditUserModal(userId) {\n    const user = (this.adminUsersList || []).find(u => u.id === userId);\n    if (!user) return;\n    const modal = document.getElementById('modalAdminEditUser');\n    if (!modal) return;\n\n    this.adminEditingTargetUser = user;\n    this.adminEditAvatarValue = user.avatar_url || null;\n\n    const alertEl = document.getElementById('adminEditUserAlert');\n    const noticeEl = document.getElementById('adminEditUserNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    document.getElementById('adminEditUserId').value = user.id;\n    document.getElementById('adminEditUserName').value = user.name || '';\n    document.getElementById('adminEditUserEmail').value = user.email || '';\n    document.getElementById('adminEditUserOrg').value = user.organization || '';\n    document.getElementById('adminEditUserRole').value = user.role || '';\n    document.getElementById('adminEditUserPhone').value = user.phone || '';\n    document.getElementById('adminEditUserPrivilege').value = user.is_admin ? 'admin' : 'user';\n    document.getElementById('adminEditUserStatus').value = user.is_disabled ? 'disabled' : 'active';\n\n    this.renderAdminEditAvatarPreview();\n    this.renderAvatarPresets('adminEditPresetIcons', (icon) => {\n      this.adminEditAvatarValue = icon;\n      this.renderAdminEditAvatarPreview();\n    });\n\n    modal.classList.add('show');\n  }\n\n  renderAdminEditAvatarPreview() {\n    const preview = document.getElementById('adminEditAvatarPreview');\n    if (!preview || !this.adminEditingTargetUser) return;\n    const tempUser = {\n      ...this.adminEditingTargetUser,\n      avatar_url: this.adminEditAvatarValue\n    };\n    preview.innerHTML = this.getUserAvatarHtml(tempUser, 60);\n  }\n\n  async saveAdminEditUser(e) {\n    if (e && e.preventDefault) e.preventDefault();\n    if (!this.adminEditingTargetUser) return;\n    const alertEl = document.getElementById('adminEditUserAlert');\n    const noticeEl = document.getElementById('adminEditUserNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    const userId = this.adminEditingTargetUser.id;\n    const name = document.getElementById('adminEditUserName')?.value?.trim();\n    const email = document.getElementById('adminEditUserEmail')?.value?.trim();\n    const organization = document.getElementById('adminEditUserOrg')?.value?.trim();\n    const role = document.getElementById('adminEditUserRole')?.value?.trim();\n    const phone = document.getElementById('adminEditUserPhone')?.value?.trim();\n    const is_admin = document.getElementById('adminEditUserPrivilege')?.value === 'admin';\n    const is_disabled = document.getElementById('adminEditUserStatus')?.value === 'disabled';\n\n    if (!name || !email) {\n      if (alertEl) {\n        alertEl.textContent = 'Name and email are required fields.';\n        alertEl.style.display = 'block';\n      }\n      return;\n    }\n\n    try {\n      const res = await this.apiFetch(`/api/admin/users/${userId}`, {\n        method: 'PUT',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          name,\n          email,\n          organization: organization || null,\n          role: role || null,\n          phone: phone || null,\n          avatar_url: this.adminEditAvatarValue || null,\n          is_admin,\n          is_disabled\n        })\n      });\n      let data;\n      try {\n        data = await res.json();\n      } catch (jsonErr) {\n        const errText = await res.text().catch(() => '');\n        throw new Error(errText || `Server returned error status ${res.status}`);\n      }\n      if (!res.ok) {\n        throw new Error((data && data.error) || 'Failed to update user profile.');\n      }\n\n      this.showNotification(data.message || 'User updated successfully.', 'success');\n      const modal = document.getElementById('modalAdminEditUser');\n      if (modal) modal.classList.remove('show');\n      await this.loadAdminUsers();\n      if (this.currentUser && this.currentUser.id === userId) {\n        this.currentUser = { ...this.currentUser, ...data.user };\n        this.updateUserUI();\n      }\n    } catch (err) {\n      console.error(err);\n      if (alertEl) {\n        alertEl.textContent = err.message;\n        alertEl.style.display = 'block';\n      }\n    }\n  }\n\n  async deleteAdminUser() {\n    if (!this.adminEditingTargetUser) return;\n    const user = this.adminEditingTargetUser;\n    if (!confirm(`Are you absolutely sure you want to permanently delete user \"${user.name}\" (${user.email})?\\n\\nThis will remove their account and all active sessions permanently.`)) {\n      return;\n    }\n\n    const alertEl = document.getElementById('adminEditUserAlert');\n    try {\n      const res = await this.apiFetch(`/api/admin/users/${user.id}`, {\n        method: 'DELETE'\n      });\n      const data = await res.json();\n      if (!res.ok) {\n        throw new Error(data.error || 'Failed to delete user.');\n      }\n\n      this.showNotification(data.message || 'User deleted successfully.', 'success');\n      const modal = document.getElementById('modalAdminEditUser');\n      if (modal) modal.classList.remove('show');\n      await this.loadAdminUsers();\n    } catch (err) {\n      console.error(err);\n      if (alertEl) {\n        alertEl.textContent = err.message;\n        alertEl.style.display = 'block';\n      }\n      this.showNotification(err.message, 'error');\n    }\n  }\n\n  // ==========================================================================\n  // ADMIN CSV IMPORT & EXPORT (REQUIREMENT 3)\n  // ==========================================================================\n  async exportAdminUsersCsv() {\n    try {\n      this.showNotification('Preparing CSV export...', 'info');\n      const res = await this.apiFetch('/api/admin/users/export.csv');\n      if (!res.ok) {\n        const err = await res.json().catch(() => ({}));\n        throw new Error(err.error || 'Failed to export users to CSV.');\n      }\n      const csvText = await res.text();\n      const blob = new Blob([csvText], { type: 'text/csv;charset=utf-8;' });\n      const url = URL.createObjectURL(blob);\n      const link = document.createElement('a');\n      link.setAttribute('href', url);\n      link.setAttribute('download', `bowtie_users_${new Date().toISOString().slice(0, 10)}.csv`);\n      document.body.appendChild(link);\n      link.click();\n      document.body.removeChild(link);\n      URL.revokeObjectURL(url);\n      this.showNotification('Users successfully exported to CSV!', 'success');\n    } catch (err) {\n      console.error(err);\n      this.showNotification(err.message, 'error');\n    }\n  }\n\n  openAdminImportUsersModal() {\n    const modal = document.getElementById('modalAdminImportUsers');\n    if (!modal) return;\n    const alertEl = document.getElementById('adminImportAlert');\n    const noticeEl = document.getElementById('adminImportNotice');\n    const resultsBox = document.getElementById('adminImportResultsBox');\n    const fileInput = document.getElementById('adminImportCsvFileInput');\n    const textArea = document.getElementById('adminImportCsvTextArea');\n\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n    if (resultsBox) resultsBox.style.display = 'none';\n    if (fileInput) fileInput.value = '';\n    if (textArea) textArea.value = '';\n\n    modal.classList.add('show');\n  }\n\n  downloadCsvTemplate() {\n    const templateContent = \"email,name,role,organization,phone,is_admin,is_disabled,avatar_url\\r\\njane.doe@example.com,Jane Doe,Lead Risk Assessor,Offshore Safe,+44 7700 900111,false,false,👷\\r\\njohn.smith@example.com,John Smith,Cyber Barrier Specialist,HQ Cyber,+44 7700 900222,true,false,⚡\\r\\n\";\n    const blob = new Blob([templateContent], { type: 'text/csv;charset=utf-8;' });\n    const url = URL.createObjectURL(blob);\n    const link = document.createElement('a');\n    link.setAttribute('href', url);\n    link.setAttribute('download', 'bowtie_users_template.csv');\n    document.body.appendChild(link);\n    link.click();\n    document.body.removeChild(link);\n    URL.revokeObjectURL(url);\n  }\n\n  async submitAdminImportCsv() {\n    const alertEl = document.getElementById('adminImportAlert');\n    const noticeEl = document.getElementById('adminImportNotice');\n    const resultsBox = document.getElementById('adminImportResultsBox');\n    const summaryEl = document.getElementById('adminImportResultsSummary');\n    const errorsEl = document.getElementById('adminImportResultsErrors');\n    const textArea = document.getElementById('adminImportCsvTextArea');\n    const btn = document.getElementById('btnSubmitAdminImportCsv');\n\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n    if (resultsBox) resultsBox.style.display = 'none';\n\n    const csvData = (textArea ? textArea.value : '').trim();\n    if (!csvData) {\n      if (alertEl) {\n        alertEl.textContent = 'Please choose a CSV file or paste CSV text to import.';\n        alertEl.style.display = 'block';\n      }\n      return;\n    }\n\n    if (btn) btn.disabled = true;\n\n    try {\n      const res = await this.apiFetch('/api/admin/users/import', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ csv_data: csvData })\n      });\n      const data = await res.json();\n      if (!res.ok) {\n        throw new Error(data.error || 'CSV import failed.');\n      }\n\n      if (resultsBox && summaryEl) {\n        resultsBox.style.display = 'block';\n        summaryEl.textContent = `✓ Import complete: ${data.created_count} user(s) created, ${data.updated_count} user(s) updated (${data.total_processed} total processed).`;\n        if (errorsEl) {\n          if (data.errors && data.errors.length > 0) {\n            errorsEl.innerHTML = `<strong>Warnings:</strong><br>${data.errors.map(e => `• ${this.escapeHtml(e)}`).join('<br>')}`;\n            errorsEl.style.display = 'block';\n          } else {\n            errorsEl.innerHTML = '';\n            errorsEl.style.display = 'none';\n          }\n        }\n      }\n\n      this.showNotification(`CSV import complete: ${data.created_count} created, ${data.updated_count} updated`, 'success');\n      await this.loadAdminUsers();\n    } catch (err) {\n      console.error(err);\n      if (alertEl) {\n        alertEl.textContent = err.message;\n        alertEl.style.display = 'block';\n      }\n    } finally {\n      if (btn) btn.disabled = false;\n    }\n  }\n\n  async loadAdminConfig() {\n    const alertEl = document.getElementById('adminPortalAlert');\n    try {\n      const res = await this.apiFetch('/api/admin/config');\n      if (!res.ok) {\n        const err = await res.json().catch(() => ({}));\n        throw new Error(err.error || 'Failed to load instance configuration.');\n      }\n      const cfg = await res.json();\n      this.currentAdminConfig = cfg;\n\n      // General\n      const instName = document.getElementById('cfgInstanceName');\n      const instUrl = document.getElementById('cfgInstanceUrl');\n      const regMode = document.getElementById('cfgRegistrationMode');\n      const domainGrp = document.getElementById('cfgDomainGroup');\n      const allowedDom = document.getElementById('cfgAllowedDomain');\n\n      if (instName) instName.value = cfg.instance_name || '';\n      if (instUrl) instUrl.value = cfg.base_url || '';\n      if (regMode) {\n        regMode.value = cfg.registration_mode || 'open';\n        if (domainGrp) {\n          domainGrp.style.display = cfg.registration_mode === 'restricted_domain' ? 'block' : 'none';\n        }\n      }\n      if (allowedDom) allowedDom.value = (cfg.allowed_domains || []).join(', ');\n      const enableDir = document.getElementById('cfgEnableDirectory');\n      if (enableDir) enableDir.checked = Boolean(cfg.enable_company_directory);\n\n      // SMTP\n      const smtp = cfg.smtp || {};\n      const smtpEn = document.getElementById('cfgSmtpEnabled');\n      const smtpHost = document.getElementById('cfgSmtpHost');\n      const smtpPort = document.getElementById('cfgSmtpPort');\n      const smtpSec = document.getElementById('cfgSmtpSecurity');\n      const smtpFromEmail = document.getElementById('cfgSmtpFromEmail');\n      const smtpFromName = document.getElementById('cfgSmtpFromName');\n      const smtpUser = document.getElementById('cfgSmtpUsername');\n      const smtpPass = document.getElementById('cfgSmtpPassword');\n\n      if (smtpEn) smtpEn.checked = Boolean(smtp.enabled);\n      if (smtpHost) smtpHost.value = smtp.host || 'smtp.gmail.com';\n      if (smtpPort) smtpPort.value = smtp.port || 587;\n      if (smtpSec) smtpSec.value = smtp.encryption || 'starttls';\n      if (smtpFromEmail) smtpFromEmail.value = smtp.from_email || '';\n      if (smtpFromName) smtpFromName.value = smtp.from_name || 'Bowtie RISK Security';\n      if (smtpUser) smtpUser.value = smtp.username || '';\n      if (smtpPass) {\n        smtpPass.value = '';\n        smtpPass.placeholder = smtp.has_password ? '•••••••••••• (Leave blank to keep existing password)' : 'Enter SMTP password / App Password';\n      }\n\n      // SSO\n      const sso = cfg.sso || {};\n      const isGoogle = sso.provider === 'google' || !sso.provider;\n      const isMs = sso.provider === 'microsoft';\n\n      const gEn = document.getElementById('cfgGoogleSsoEnabled');\n      const gId = document.getElementById('cfgGoogleClientId');\n      const gSec = document.getElementById('cfgGoogleClientSecret');\n      const gUri = document.getElementById('cfgGoogleRedirectUri');\n\n      if (gEn) gEn.checked = Boolean(sso.enabled && isGoogle);\n      if (gId) gId.value = isGoogle ? (sso.client_id || '') : '';\n      if (gSec) {\n        gSec.value = '';\n        gSec.placeholder = (isGoogle && sso.has_client_secret) ? '•••••••••••• (Stored securely; leave blank to keep)' : 'Enter Client Secret';\n      }\n      if (gUri) gUri.value = isGoogle ? (sso.redirect_uri || '') : '';\n\n      const mEn = document.getElementById('cfgMicrosoftSsoEnabled');\n      const mId = document.getElementById('cfgMicrosoftClientId');\n      const mSec = document.getElementById('cfgMicrosoftClientSecret');\n\n      if (mEn) mEn.checked = Boolean(sso.enabled && isMs);\n      if (mId) mId.value = isMs ? (sso.client_id || '') : '';\n      if (mSec) {\n        mSec.value = '';\n        mSec.placeholder = (isMs && sso.has_client_secret) ? '•••••••••••• (Stored securely; leave blank to keep)' : 'Enter Client Secret';\n      }\n\n    } catch (e) {\n      console.error(e);\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n    }\n  }\n\n  async saveAdminConfig(e) {\n    if (e) e.preventDefault();\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    const allowedDomVal = document.getElementById('cfgAllowedDomain')?.value?.trim();\n    const allowedDomains = allowedDomVal ? allowedDomVal.split(',').map(s => s.trim()).filter(Boolean) : [];\n\n    const smtpPass = document.getElementById('cfgSmtpPassword')?.value?.trim();\n    const gSec = document.getElementById('cfgGoogleClientSecret')?.value?.trim();\n    const mSec = document.getElementById('cfgMicrosoftClientSecret')?.value?.trim();\n\n    const googleEnabled = Boolean(document.getElementById('cfgGoogleSsoEnabled')?.checked);\n    const msEnabled = Boolean(document.getElementById('cfgMicrosoftSsoEnabled')?.checked);\n    const provider = msEnabled ? 'microsoft' : 'google';\n    const ssoClientId = msEnabled\n      ? (document.getElementById('cfgMicrosoftClientId')?.value?.trim() || '')\n      : (document.getElementById('cfgGoogleClientId')?.value?.trim() || '');\n    const ssoClientSecret = msEnabled ? mSec : gSec;\n    const ssoRedirectUri = msEnabled\n      ? null\n      : (document.getElementById('cfgGoogleRedirectUri')?.value?.trim() || null);\n\n    const payload = {\n      instance_name: document.getElementById('cfgInstanceName')?.value?.trim() || 'Bowtie RISK Enterprise',\n      base_url: document.getElementById('cfgInstanceUrl')?.value?.trim() || (window.location.origin.includes('localhost') ? window.location.origin : 'https://app.bowtierisk.com'),\n      registration_mode: document.getElementById('cfgRegistrationMode')?.value || 'open',\n      allowed_domains: allowedDomains.length > 0 ? allowedDomains : null,\n      enable_company_directory: Boolean(document.getElementById('cfgEnableDirectory')?.checked),\n      smtp: {\n        enabled: Boolean(document.getElementById('cfgSmtpEnabled')?.checked),\n        host: document.getElementById('cfgSmtpHost')?.value?.trim() || 'smtp.gmail.com',\n        port: parseInt(document.getElementById('cfgSmtpPort')?.value || '587', 10),\n        username: document.getElementById('cfgSmtpUsername')?.value?.trim() || '',\n        password: smtpPass || null,\n        from_email: document.getElementById('cfgSmtpFromEmail')?.value?.trim() || 'noreply@bowtierisk.com',\n        from_name: document.getElementById('cfgSmtpFromName')?.value?.trim() || 'Bowtie RISK Security',\n        encryption: document.getElementById('cfgSmtpSecurity')?.value || 'starttls',\n      },\n      sso: {\n        enabled: googleEnabled || msEnabled,\n        provider: provider,\n        client_id: ssoClientId,\n        client_secret: ssoClientSecret || null,\n        auth_url: null,\n        token_url: null,\n        redirect_uri: ssoRedirectUri,\n        allow_password_fallback: true,\n      }\n    };\n\n    try {\n      const res = await this.apiFetch('/api/admin/config', {\n        method: 'PUT',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify(payload)\n      });\n      const data = await res.json();\n      if (!res.ok) {\n        throw new Error(data.error || 'Failed to save instance configuration.');\n      }\n      this.showNotification('Instance configuration saved securely!', 'success');\n      if (noticeEl) {\n        noticeEl.textContent = 'Configuration updated and applied successfully.';\n        noticeEl.style.display = 'block';\n      }\n      await this.loadAdminConfig();\n    } catch (e) {\n      console.error(e);\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n      this.showNotification(e.message, 'error');\n    }\n  }\n\n  async sendAdminTestEmail() {\n    const targetInput = document.getElementById('cfgTestEmailTarget');\n    const targetEmail = targetInput?.value?.trim() || (this.currentUser ? this.currentUser.email : '');\n    const statusEl = document.getElementById('smtpTestStatus');\n    const btn = document.getElementById('btnAdminTestEmail');\n\n    if (!targetEmail) {\n      if (statusEl) {\n        statusEl.textContent = 'Please enter a recipient email address.';\n        statusEl.style.color = '#dc2626';\n        statusEl.style.display = 'block';\n      }\n      return;\n    }\n\n    if (statusEl) {\n      statusEl.textContent = `Connecting to SMTP host and dispatching test email to ${targetEmail}...`;\n      statusEl.style.color = '#2563eb';\n      statusEl.style.display = 'block';\n    }\n    if (btn) btn.disabled = true;\n\n    try {\n      const res = await this.apiFetch('/api/admin/config/test-email', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ to: targetEmail })\n      });\n      const data = await res.json();\n      if (!res.ok) {\n        throw new Error(data.error || 'SMTP test email dispatch failed.');\n      }\n      if (statusEl) {\n        statusEl.textContent = `✓ ${data.message || 'Test email dispatched successfully! Check inbox.'}`;\n        statusEl.style.color = '#16a34a';\n      }\n      this.showNotification('Test email sent successfully!', 'success');\n    } catch (e) {\n      console.error(e);\n      if (statusEl) {\n        statusEl.textContent = `✗ Error: ${e.message}`;\n        statusEl.style.color = '#dc2626';\n      }\n      this.showNotification(`Test email failed: ${e.message}`, 'error');\n    } finally {\n      if (btn) btn.disabled = false;\n    }\n  }\n\n  applySmtpPreset(presetName) {\n    const host = document.getElementById('cfgSmtpHost');\n    const port = document.getElementById('cfgSmtpPort');\n    const sec = document.getElementById('cfgSmtpSecurity');\n    if (presetName === 'gmail') {\n      if (host) host.value = 'smtp.gmail.com';\n      if (port) port.value = '587';\n      if (sec) sec.value = 'starttls';\n    } else if (presetName === 'office365') {\n      if (host) host.value = 'smtp.office365.com';\n      if (port) port.value = '587';\n      if (sec) sec.value = 'starttls';\n    }\n  }\n\n  // ==========================================================================\n  // ELEMENT SUGGESTIONS POOL MANAGEMENT\n  // ==========================================================================\n  getDefaultElementSuggestions() {\n    return {\n      hazard: [\n        'High Pressure Hydrocarbon Gas (75 bar)',\n        'Toxic Chemical Storage (Chlorine / Ammonia)',\n        'Aviation Jet Fuel Bulk Storage Tank',\n        'High Voltage Electrical Switchgear (33kV)',\n        'Heavy Equipment & Work at Height Operations',\n        'Pressurized Steam Boiler System (40 bar)',\n        'Cryogenic Liquefied Natural Gas (LNG)',\n        'Offshore Hydrogen Sulfide (H2S) Sour Gas',\n      ],\n      top_event: [\n        'Loss of Primary Containment from Vessel',\n        'Catastrophic Pipeline Rupture & Gas Release',\n        'High Voltage Electrical Arc Flash & Fire',\n        'Structural Failure / Collapse of Work Platform',\n        'Thermal Runaway Reaction in Reactor',\n        'Overfill / Tank Shell Breach with Liquid Overflow',\n        'Uncontrolled Wellbore Blowout to Surface',\n        'Turbine Overspeed & Rotor Disintegration',\n      ],\n      threat: [\n        'Internal Acidic / Sour Gas Metal Corrosion',\n        'Operator Manual Valve Alignment Error',\n        'External Mechanical Impact from Dropped Crane Load',\n        'High-Pressure Gas Compression Surge',\n        'Instrument Air Utility Complete Pressure Loss',\n        'Flange Gasket Degradation & Thermal Shock',\n        'Upstream Control Valve Failed Full Open',\n        'Direct Lightning Strike to Exposed Storage Tank',\n        'Fatigue Cracking from Cyclic Vibration',\n      ],\n      threat_barrier: [\n        'High-Pressure Trip Switch (PSHH-101) with SIL-2 Interlock',\n        'Independent High-Level Radar Gauge with Auto-Shutdown',\n        'Certified Spring-Loaded Pressure Relief Valve (PSV-201)',\n        'Automated Fail-Closed Emergency Shutdown Valve (ESDV)',\n        'Chemical Corrosion Inhibitor Automated Dosing Skid',\n        'Scheduled Ultrasonic Wall Thickness Non-Destructive Testing',\n        'Dual Redundant Bursting Disc with Pressure Sensor',\n        'Mechanical Trapped-Key Valve Interlock System',\n        'Independent Flow Limiting Restriction Orifice (RO-301)',\n      ],\n      escalation_factor: [\n        'Sub-Zero Ambient Freezing / Severe Frost',\n        'Instrumentation Calibration Drift / Sensing Line Clog',\n        'Maintenance Bypass Key Left Inserted in Override',\n        'Offsite Main Power Grid Total Blackout',\n        'Extreme Seismic Tremor / Earth Movement',\n        'Marine Atmospheric Salt Corrosion on Valve Stem',\n        'Operator Alarm Fatigue from Nuisance Alarming',\n        'Severe Sand Storm / Air Intake Filter Clogging',\n      ],\n      mitigation: [\n        'Self-Regulating Electric Heat Tracing with Insulation Jacket',\n        'Quarterly Proof-Testing & Sensor Recalibration Schedule',\n        'Strict Permit-to-Work with Administrative Key Control',\n        'Uninterruptible Power Supply (UPS) with Battery Backup',\n        'Flexible Stainless Steel Bellows & Seismic Snubbers',\n        'Grease Injection Port & Weather Protective Stem Hood',\n        'Alarm Rationalization Program & Dynamic Staged Suppression',\n        'Automatic Self-Cleaning Dual Intake Air Filtration',\n      ],\n      outcome_barrier: [\n        'Automated High-Rate Deluge Water Spray System',\n        'SIL-2 Flammable / Toxic Gas Detector with Audible Siren',\n        'Rapid Depressuring Blowdown Valve to Flare Header',\n        'Blast-Resistant Reinforced Concrete Firewall',\n        'Secondary Concrete Bund Containment with Sump Pump',\n        'Emergency Foam Induction Pourer on Tank Rim',\n        'Remote ESD Pushbutton at Facility Evacuation Muster Point',\n        'Passive PFP Epoxy Intumescent Coating on Steelwork',\n      ],\n      outcome: [\n        'Vapor Cloud Explosion (VCE) & Severe Asset Damage',\n        'Toxic Gas Inhalation with Acute Personnel Casualties',\n        'Major Offshore Marine Environmental Oil Spill',\n        'Unignited Atmospheric Gas Release & Production Outage',\n        'Boiling Liquid Expanding Vapor Explosion (BLEVE)',\n        'Prolonged Jet Fire with Adjacent Vessel Flame Impingement',\n        'Severe Regulatory Sanction, Fine & License Revocation',\n        'Catastrophic Structural Impairment & Total Loss of Asset',\n      ],\n    };\n  }\n\n  getElementSuggestions(nodeType) {\n    if (this.elementSuggestions && Array.isArray(this.elementSuggestions[nodeType]) && this.elementSuggestions[nodeType].length > 0) {\n      return this.elementSuggestions[nodeType];\n    }\n    if (this.workspace && this.workspace.element_suggestions && Array.isArray(this.workspace.element_suggestions[nodeType]) && this.workspace.element_suggestions[nodeType].length > 0) {\n      return this.workspace.element_suggestions[nodeType];\n    }\n    if (this.workspace && this.workspace.instance_config && this.workspace.instance_config.element_suggestions && Array.isArray(this.workspace.instance_config.element_suggestions[nodeType]) && this.workspace.instance_config.element_suggestions[nodeType].length > 0) {\n      return this.workspace.instance_config.element_suggestions[nodeType];\n    }\n    const defaults = this.getDefaultElementSuggestions();\n    return defaults[nodeType] || [];\n  }\n\n  renderAdminSuggestions() {\n    const grid = document.getElementById('adminSuggestionsGrid');\n    if (!grid) return;\n\n    if (!this.elementSuggestions) {\n      this.elementSuggestions = this.getDefaultElementSuggestions();\n    }\n\n    const typeConfigs = [\n      {\n        key: 'hazard',\n        label: 'Hazard',\n        color: '#eab308',\n        desc: 'Source of potential harm or high-energy material (e.g. hydrocarbons, pressure, toxic chemicals).',\n      },\n      {\n        key: 'top_event',\n        label: 'Top Event',\n        color: '#f97316',\n        desc: 'Loss of control or primary containment occurring at the center knot of the bowtie.',\n      },\n      {\n        key: 'threat',\n        label: 'Threat',\n        color: '#2563eb',\n        desc: 'Direct initiating operational cause or mechanism that can lead to the Top Event.',\n      },\n      {\n        key: 'threat_barrier',\n        label: 'Threat Barrier (Prevention)',\n        color: '#10b981',\n        desc: 'Prevention barrier or SIL safety interlock that prevents the threat from escalating.',\n      },\n      {\n        key: 'escalation_factor',\n        label: 'Escalation Factor',\n        color: '#8b5cf6',\n        desc: 'Condition or defeating mechanism that impairs or degrades a primary barrier.',\n      },\n      {\n        key: 'mitigation',\n        label: 'Mitigation Barrier',\n        color: '#059669',\n        desc: 'Control or maintenance barrier that defends the primary barrier against the escalation factor.',\n      },\n      {\n        key: 'outcome_barrier',\n        label: 'Outcome Barrier (Mitigation)',\n        color: '#10b981',\n        desc: 'Recovery, containment or deluge barrier that minimizes consequences after release.',\n      },\n      {\n        key: 'outcome',\n        label: 'Outcome / Consequence',\n        color: '#ef4444',\n        desc: 'Final severe adverse consequence or loss (personnel injury, asset damage, environmental harm).',\n      },\n    ];\n\n    grid.innerHTML = '';\n\n    typeConfigs.forEach(cfg => {\n      const suggestions = Array.isArray(this.elementSuggestions[cfg.key])\n        ? this.elementSuggestions[cfg.key]\n        : [];\n\n      const card = document.createElement('div');\n      card.className = 'suggestion-card';\n\n      card.innerHTML = `\n        <div class=\"suggestion-card-header\">\n          <div class=\"suggestion-card-title-group\">\n            <span class=\"suggestion-card-badge\" style=\"background-color: ${cfg.color};\">${this.escapeHtml(cfg.label)}</span>\n          </div>\n          <span class=\"suggestion-card-count\" id=\"count-${cfg.key}\">${suggestions.length} example${suggestions.length === 1 ? '' : 's'}</span>\n        </div>\n        <div class=\"suggestion-card-desc\">${this.escapeHtml(cfg.desc)}</div>\n        <div class=\"suggestion-card-body\">\n          <div class=\"suggestion-chip-list\" id=\"chips-${cfg.key}\">\n            ${suggestions.map((item, idx) => `\n              <div class=\"suggestion-chip\" data-idx=\"${idx}\">\n                <span class=\"suggestion-chip-text\" title=\"${this.escapeHtml(item)}\">${this.escapeHtml(item)}</span>\n                <button type=\"button\" class=\"suggestion-remove-btn\" title=\"Remove suggestion\" data-key=\"${cfg.key}\" data-idx=\"${idx}\">&times;</button>\n              </div>\n            `).join('')}\n          </div>\n          <div class=\"suggestion-add-row\">\n            <input type=\"text\" class=\"suggestion-add-input\" id=\"input-${cfg.key}\" placeholder=\"Add example ${this.escapeHtml(cfg.label.toLowerCase())}...\" />\n            <button type=\"button\" class=\"suggestion-add-btn\" id=\"btnAdd-${cfg.key}\">+ Add</button>\n          </div>\n        </div>\n      `;\n\n      // Event listeners for remove buttons\n      const chipList = card.querySelector(`#chips-${cfg.key}`);\n      chipList.addEventListener('click', (e) => {\n        const removeBtn = e.target.closest('.suggestion-remove-btn');\n        if (removeBtn) {\n          const idx = parseInt(removeBtn.getAttribute('data-idx'), 10);\n          this.removeSuggestion(cfg.key, idx);\n        }\n      });\n\n      // Add button & Enter key\n      const addInput = card.querySelector(`#input-${cfg.key}`);\n      const addBtn = card.querySelector(`#btnAdd-${cfg.key}`);\n\n      const doAdd = () => {\n        const val = (addInput.value || '').trim();\n        if (val) {\n          this.addSuggestion(cfg.key, val);\n          addInput.value = '';\n          addInput.focus();\n        }\n      };\n\n      addBtn.addEventListener('click', doAdd);\n      addInput.addEventListener('keydown', (e) => {\n        if (e.key === 'Enter') {\n          e.preventDefault();\n          doAdd();\n        }\n      });\n\n      grid.appendChild(card);\n    });\n  }\n\n  addSuggestion(key, text) {\n    if (!this.elementSuggestions) {\n      this.elementSuggestions = this.getDefaultElementSuggestions();\n    }\n    if (!Array.isArray(this.elementSuggestions[key])) {\n      this.elementSuggestions[key] = [];\n    }\n    if (!this.elementSuggestions[key].includes(text)) {\n      this.elementSuggestions[key].push(text);\n      this.renderAdminSuggestions();\n    }\n  }\n\n  removeSuggestion(key, index) {\n    if (this.elementSuggestions && Array.isArray(this.elementSuggestions[key])) {\n      this.elementSuggestions[key].splice(index, 1);\n      this.renderAdminSuggestions();\n    }\n  }\n\n  resetAdminSuggestions() {\n    if (confirm('Reset element suggestions pool to industry default examples? This will replace unsaved modifications.')) {\n      this.elementSuggestions = this.getDefaultElementSuggestions();\n      this.renderAdminSuggestions();\n      const noticeEl = document.getElementById('adminPortalNotice');\n      if (noticeEl) {\n        noticeEl.textContent = 'Suggestions pool reset to defaults. Click \"Save Suggestions Pool\" to persist.';\n        noticeEl.style.display = 'block';\n      }\n    }\n  }\n\n  async saveAdminSuggestions() {\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    try {\n      const res = await this.apiFetch('/api/admin/suggestions', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ suggestions: this.elementSuggestions }),\n      });\n      if (!res.ok) {\n        const err = await res.json().catch(() => ({}));\n        throw new Error(err.error || 'Failed to save suggestions pool.');\n      }\n      if (this.workspace) {\n        this.workspace.element_suggestions = JSON.parse(JSON.stringify(this.elementSuggestions));\n        if (this.workspace.nodes) {\n          this.workspace.nodes.forEach(n => {\n            delete n._suggestedPlaceholder;\n          });\n        }\n      }\n      if (this.canvas) {\n        this.canvas.render();\n      }\n      if (noticeEl) {\n        noticeEl.textContent = 'Element suggestions pool saved successfully!';\n        noticeEl.style.display = 'block';\n      }\n      this.showNotification('Element suggestions pool saved successfully!', 'success');\n    } catch (e) {\n      console.error(e);\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n    }\n  }\n\n  // ==========================================================================\n  // ON-PREMISE LICENSING & SYSTEM UPDATES (ONLINE AUTO-UPDATE & OFFLINE .BTUPDATE)\n  // ==========================================================================\n  async loadAdminLicenseAndUpdateStatus() {\n    try {\n      const res = await this.apiFetch('/api/licensing/status');\n      if (res.ok) {\n        const status = await res.json();\n        if (this.workspace) {\n          this.workspace.license_status = status;\n        }\n        this.renderAdminLicenseAndUpdateUI(status);\n        this.updateUnlicensedBannerUI();\n      }\n    } catch (e) {\n      console.error('Failed to load license status:', e);\n    }\n  }\n\n  renderAdminLicenseAndUpdateUI(status) {\n    if (!status) return;\n    const badge = document.getElementById('adminLicenseStatusBadge');\n    const modeLabel = document.getElementById('adminLicenseModeLabel');\n    const editLabel = document.getElementById('adminLicenseEditLabel');\n    const orgLabel = document.getElementById('adminLicenseOrgLabel');\n    const idLabel = document.getElementById('adminLicenseIdLabel');\n    const expLabel = document.getElementById('adminLicenseExpiresLabel');\n    const seatsLabel = document.getElementById('adminLicenseSeatsLabel');\n    const msgEl = document.getElementById('adminLicenseStatusMsg');\n\n    if (badge) {\n      if (status.is_licensed) {\n        badge.textContent = status.is_onprem ? 'Licensed (Active)' : 'Cloud Instance (Licensed)';\n        badge.style.background = '#dcfce7';\n        badge.style.color = '#166534';\n      } else {\n        badge.textContent = 'Unlicensed (Read-Only)';\n        badge.style.background = '#fef2f2';\n        badge.style.color = '#991b1b';\n      }\n    }\n\n    if (modeLabel) {\n      modeLabel.textContent = status.is_onprem ? 'On-Premise / Self-Hosted' : 'Cloud Hosted';\n    }\n    if (editLabel) {\n      editLabel.textContent = status.can_edit ? 'Unlocked (Full Create & Edit)' : 'Locked (Read-Only Mode)';\n      editLabel.style.color = status.can_edit ? '#16a34a' : '#dc2626';\n    }\n    if (orgLabel) {\n      orgLabel.textContent = status.organization || (status.is_onprem ? 'Unlicensed' : 'Bowtie RISK Cloud');\n    }\n    if (idLabel) {\n      idLabel.textContent = status.license_id ? `${status.license_id} (${status.tier || 'onprem'})` : (status.tier || '—');\n    }\n    if (expLabel) {\n      expLabel.textContent = status.expires_at ? status.expires_at.slice(0, 10) : (status.is_onprem ? '—' : 'Perpetual');\n    }\n    if (seatsLabel) {\n      seatsLabel.textContent = (!status.max_users || status.max_users === 0) ? 'Unlimited' : `${status.max_users} Users`;\n    }\n    if (msgEl) {\n      msgEl.textContent = status.message || '';\n    }\n\n    // Update settings UI\n    const verBadge = document.getElementById('adminUpdateVersionBadge');\n    if (verBadge && status.current_version) {\n      verBadge.textContent = `v${status.current_version}`;\n    }\n    const autoToggle = document.getElementById('adminAutoUpdateToggle');\n    if (autoToggle) {\n      autoToggle.checked = Boolean(status.auto_update_enabled);\n    }\n    const urlInput = document.getElementById('adminUpdateServerUrlInput');\n    if (urlInput && status.update_server_url) {\n      urlInput.value = status.update_server_url;\n    }\n    const lastCheckEl = document.getElementById('adminUpdateLastCheckLabel');\n    if (lastCheckEl) {\n      const overlayTxt = status.installed_update_version\n        ? `Active overlay: v${status.installed_update_version}`\n        : `Built-in release: v${status.current_version || '1.1.1111'}`;\n      const checkTxt = status.last_update_check\n        ? ` • Last update: ${new Date(status.last_update_check).toLocaleString()}`\n        : '';\n      lastCheckEl.textContent = `${overlayTxt}${checkTxt}`;\n    }\n  }\n\n  async activateAdminLicense() {\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    const keyInput = document.getElementById('adminLicenseKeyInput');\n    const rawKey = (keyInput ? keyInput.value : '').trim();\n    if (!rawKey) {\n      if (alertEl) {\n        alertEl.textContent = 'Please upload a .btlic file or paste a BTRISK-v1 license key first.';\n        alertEl.style.display = 'block';\n      }\n      return;\n    }\n\n    try {\n      const res = await this.apiFetch('/api/admin/license', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ license_key: rawKey }),\n      });\n      const data = await res.json().catch(() => ({}));\n      if (!res.ok) {\n        throw new Error(data.error || 'Failed to verify or activate license key.');\n      }\n\n      if (this.workspace && data.license_status) {\n        this.workspace.license_status = data.license_status;\n      }\n      this.renderAdminLicenseAndUpdateUI(data.license_status);\n      this.updateUnlicensedBannerUI();\n      this.updateDiagramPermissionUI();\n      if (this.documents) this.documents.render();\n\n      if (noticeEl) {\n        noticeEl.textContent = data.message || 'License verified and activated! Full Bowtie creation and editing is now unlocked.';\n        noticeEl.style.display = 'block';\n      }\n      this.showNotification('On-Premise License activated! Diagram creation & editing unlocked.', 'success');\n    } catch (e) {\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n      this.showNotification(e.message, 'danger');\n    }\n  }\n\n  async deactivateAdminLicense() {\n    if (!confirm('Remove the active license key from this instance? On an on-premise deployment, this will return the system to Read-Only mode.')) {\n      return;\n    }\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    try {\n      const res = await this.apiFetch('/api/admin/license', { method: 'DELETE' });\n      const data = await res.json().catch(() => ({}));\n      if (!res.ok) {\n        throw new Error(data.error || 'Failed to remove license key.');\n      }\n      if (this.workspace && data.license_status) {\n        this.workspace.license_status = data.license_status;\n      }\n      const keyInput = document.getElementById('adminLicenseKeyInput');\n      if (keyInput) keyInput.value = '';\n      this.renderAdminLicenseAndUpdateUI(data.license_status);\n      this.updateUnlicensedBannerUI();\n      this.updateDiagramPermissionUI();\n      if (this.documents) this.documents.render();\n\n      if (noticeEl) {\n        noticeEl.textContent = data.message || 'License key removed.';\n        noticeEl.style.display = 'block';\n      }\n      this.showNotification('License key removed.', 'info');\n    } catch (e) {\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n    }\n  }\n\n  async saveAdminUpdateSettings() {\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    const autoToggle = document.getElementById('adminAutoUpdateToggle');\n    const urlInput = document.getElementById('adminUpdateServerUrlInput');\n\n    try {\n      const res = await this.apiFetch('/api/admin/updates/settings', {\n        method: 'PUT',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          auto_update_enabled: Boolean(autoToggle && autoToggle.checked),\n          update_server_url: urlInput ? urlInput.value.trim() : 'https://bowtierisk.com',\n        }),\n      });\n      const data = await res.json().catch(() => ({}));\n      if (!res.ok) {\n        throw new Error(data.error || 'Failed to save update policy.');\n      }\n      if (noticeEl) {\n        noticeEl.textContent = data.message || 'Update settings saved.';\n        noticeEl.style.display = 'block';\n      }\n      this.showNotification(data.message || 'Automatic update settings saved!', 'success');\n      await this.loadAdminLicenseAndUpdateStatus();\n    } catch (e) {\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n    }\n  }\n\n  async applyAdminOnlineUpdate() {\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    const btn = document.getElementById('btnAdminApplyOnlineUpdate');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n    if (btn) btn.disabled = true;\n\n    try {\n      const urlInput = document.getElementById('adminUpdateServerUrlInput');\n      const serverUrl = (urlInput && urlInput.value.trim() ? urlInput.value.trim() : window.location.origin).replace(/\\/+$/, '');\n      this.showNotification('Checking update server for signed release bundle...', 'info');\n\n      let bundle = null;\n      try {\n        const bundleRes = await fetch(`${serverUrl}/api/releases/bundle/latest`);\n        if (bundleRes.ok) {\n          bundle = await bundleRes.json();\n        }\n      } catch (_) {}\n\n      if (!bundle) {\n        const localRes = await this.apiFetch('/api/releases/bundle/latest');\n        if (localRes.ok) {\n          bundle = await localRes.json();\n        }\n      }\n\n      const applyRes = await this.apiFetch('/api/admin/updates/apply-online', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ bundle }),\n      });\n      const data = await applyRes.json().catch(() => ({}));\n      if (!applyRes.ok) {\n        throw new Error(data.error || 'Online update check failed.');\n      }\n\n      if (noticeEl) {\n        noticeEl.textContent = data.message || `Updated to version ${data.installed_version}!`;\n        noticeEl.style.display = 'block';\n      }\n      this.showNotification(data.message || 'Online update verified and installed!', 'success');\n      await this.loadAdminLicenseAndUpdateStatus();\n    } catch (e) {\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n      this.showNotification(e.message, 'danger');\n    } finally {\n      if (btn) btn.disabled = false;\n    }\n  }\n\n  async importAdminOfflineUpdate() {\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    const fileInput = document.getElementById('adminOfflineUpdateFileInput');\n    const btn = document.getElementById('btnAdminImportOfflineUpdate');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    const file = fileInput && fileInput.files && fileInput.files[0];\n    if (!file) {\n      if (alertEl) {\n        alertEl.textContent = 'Please select a signed .btupdate file to import.';\n        alertEl.style.display = 'block';\n      }\n      return;\n    }\n\n    if (btn) btn.disabled = true;\n    try {\n      const text = await file.text();\n      let bundle;\n      try {\n        bundle = JSON.parse(text);\n      } catch (_) {\n        throw new Error('Selected file is not a valid JSON .btupdate package.');\n      }\n\n      const res = await this.apiFetch('/api/admin/updates/import-offline', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ bundle }),\n      });\n      const data = await res.json().catch(() => ({}));\n      if (!res.ok) {\n        throw new Error(data.error || 'Offline update verification failed.');\n      }\n\n      if (noticeEl) {\n        noticeEl.textContent = `${data.message} (Pre-update backup saved to ${data.backup_dir || 'data/backups'})`;\n        noticeEl.style.display = 'block';\n      }\n      this.showNotification(data.message || 'Offline .btupdate package verified and applied!', 'success');\n      await this.loadAdminLicenseAndUpdateStatus();\n    } catch (e) {\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n      this.showNotification(e.message, 'danger');\n    } finally {\n      if (btn) btn.disabled = false;\n    }\n  }\n\n  async rollbackAdminUpdate() {\n    if (!confirm('Roll back any installed runtime update overlay and revert to the container built-in release?')) {\n      return;\n    }\n    const alertEl = document.getElementById('adminPortalAlert');\n    const noticeEl = document.getElementById('adminPortalNotice');\n    if (alertEl) alertEl.style.display = 'none';\n    if (noticeEl) noticeEl.style.display = 'none';\n\n    try {\n      const res = await this.apiFetch('/api/admin/updates/rollback', { method: 'POST' });\n      const data = await res.json().catch(() => ({}));\n      if (!res.ok) {\n        throw new Error(data.error || 'Failed to rollback update overlay.');\n      }\n      if (noticeEl) {\n        noticeEl.textContent = data.message || 'Rolled back to built-in version.';\n        noticeEl.style.display = 'block';\n      }\n      this.showNotification(data.message || 'Rolled back to built-in version.', 'info');\n      await this.loadAdminLicenseAndUpdateStatus();\n    } catch (e) {\n      if (alertEl) {\n        alertEl.textContent = e.message;\n        alertEl.style.display = 'block';\n      }\n    }\n  }\n}\n\n// Instantiate application on DOM ready\nwindow.addEventListener('DOMContentLoaded', () => {\n  window.app = new BowtieApp();\n  window.app.init();\n});\n",
    "js/canvas.js": "// Canvas Engine: Infinite Canvas, Pan & Zoom, Multi-diagrams, Hierarchical Drag-and-Drop, Orthogonal Wires\nclass BowtieCanvas {\n  constructor(viewportEl, stageEl, connectionsGroupEl, nodesLayerEl, app) {\n    this.viewport = viewportEl;\n    this.stage = stageEl;\n    this.connectionsGroup = connectionsGroupEl;\n    this.nodesLayer = nodesLayerEl;\n    this.simulationLayer = document.getElementById('simulationLayer');\n    this.app = app;\n\n    // Viewport transform\n    this.scale = 1.0;\n    this.panX = 0;\n    this.panY = 0;\n    this.minScale = 0.05;\n    this.maxScale = 3.0;\n\n    // Interaction state\n    this.isPanning = false;\n    this.panStartX = 0;\n    this.panStartY = 0;\n\n    // Hierarchical Dragging state\n    this.draggedNodeId = null;\n    this.dragStartMouseX = 0;\n    this.dragStartMouseY = 0;\n    this.dragInitialPositions = new Map(); // id -> { x, y }\n    this.isDraggingNode = false;\n    this.nodeDragMoved = false;\n    this.mitigationDragInitialOrder = null;\n    this.mitigationDragOrientation = null;\n    this.mitigationDragStartPos = null;\n    this.escalationFactorDragState = null;\n    this.barrierDragState = null;\n    this.remoteCursors = new Map();     // sessionId -> { el, x, y, userName, userColor }\n    this.remoteHighlights = new Map();  // sessionId -> { nodeId, userName, userColor }\n\n    this.activeNodeId = null;\n    this._lastBgScale = null;\n\n    // Auto Zoom & HUD toggle state\n    this.autoZoomEnabled = false;\n    try {\n      this.autoZoomEnabled = localStorage.getItem('bowtie_auto_zoom') === 'true';\n    } catch (_) {}\n    this._lastHudToggle = 'fit';\n    this._zoomAnimId = null;\n    this.panStartClientX = 0;\n    this.panStartClientY = 0;\n    this.panMoved = false;\n\n    this.initEvents();\n  }\n\n  initEvents() {\n    // Panning via background drag\n    this.viewport.addEventListener('mousedown', (e) => {\n      // If clicking floating toolbar, situation runner HUD, or canvas HUD, do not initiate canvas panning\n      if (e.target.closest('.canvas-floating-toolbar') || e.target.closest('.situation-runner-hud') || e.target.closest('.canvas-hud')) {\n        return;\n      }\n      // If clicking directly on viewport or stage background\n      if (e.target === this.viewport || e.target === this.stage || e.target.tagName === 'svg') {\n        if (this._zoomAnimId) {\n          cancelAnimationFrame(this._zoomAnimId);\n          this._zoomAnimId = null;\n        }\n        this.isPanning = true;\n        this.panStartX = e.clientX - this.panX;\n        this.panStartY = e.clientY - this.panY;\n        this.panStartClientX = e.clientX;\n        this.panStartClientY = e.clientY;\n        this.panMoved = false;\n        this.viewport.classList.add('panning');\n      }\n    });\n\n    window.addEventListener('mousemove', (e) => {\n      // Handle viewport pan\n      if (this.isPanning) {\n        if (Math.hypot(e.clientX - this.panStartClientX, e.clientY - this.panStartClientY) > 5) {\n          this.panMoved = true;\n        }\n        this.panX = e.clientX - this.panStartX;\n        this.panY = e.clientY - this.panStartY;\n        this.updateTransform();\n        this.updateHud(e);\n        return;\n      }\n\n      // Handle hierarchical dragging of nodes\n      if (this.isDraggingNode && this.draggedNodeId) {\n        if (!this.nodeDragMoved) {\n          if (Math.hypot(e.clientX - this.dragStartMouseX, e.clientY - this.dragStartMouseY) > 5) {\n            this.nodeDragMoved = true;\n            const el = document.getElementById(`node-${this.draggedNodeId}`);\n            if (el) el.classList.add('dragging');\n          }\n        }\n\n        if (this.nodeDragMoved) {\n          if (this._zoomAnimId) {\n            cancelAnimationFrame(this._zoomAnimId);\n            this._zoomAnimId = null;\n          }\n          const dx = (e.clientX - this.dragStartMouseX) / this.scale;\n          const dy = (e.clientY - this.dragStartMouseY) / this.scale;\n\n          const draggedNode = this.app.getNodeById(this.draggedNodeId);\n          if (draggedNode && draggedNode.node_type === 'outcome_barrier' && !this.barrierDragMode) {\n            const adx = Math.abs(dx);\n            const ady = Math.abs(dy);\n            if (adx > 8 || ady > 8) {\n              this.barrierDragMode = (ady > adx) ? 'branch' : 'barrier';\n            }\n          }\n\n          const isBarrierHorizontal = (\n            (draggedNode && draggedNode.node_type === 'threat_barrier') ||\n            (draggedNode && draggedNode.node_type === 'outcome_barrier' && this.barrierDragMode === 'barrier')\n          );\n          const barrierAttachedIds = isBarrierHorizontal && this.barrierDragState ?\n            new Set([draggedNode.id, ...(this.barrierDragState.subtrees.get(draggedNode.id) || []).map(it => it.id)]) : null;\n\n          // Move the dragged node and all its descendants!\n          for (const [id, initPos] of this.dragInitialPositions.entries()) {\n            if (barrierAttachedIds && !barrierAttachedIds.has(id)) {\n              continue;\n            }\n            const node = this.app.getNodeById(id);\n            if (node) {\n              node.x = Math.round(initPos.x + dx);\n              node.y = Math.round(initPos.y + dy);\n\n              const el = document.getElementById(`node-${id}`);\n              if (el) {\n                el.style.left = `${node.x}px`;\n                el.style.top = `${node.y}px`;\n              }\n            }\n          }\n\n          // Dynamic reordering & leveling for mitigation & barrier nodes\n          if (draggedNode && draggedNode.node_type === 'mitigation') {\n            this.handleMitigationDragReorder(draggedNode);\n          } else if (draggedNode && draggedNode.node_type === 'escalation_factor') {\n            this.handleEscalationFactorDragReorder(draggedNode);\n          } else if (draggedNode && draggedNode.node_type === 'threat_barrier') {\n            this.handleBarrierDragReorder(draggedNode);\n          } else if (draggedNode && draggedNode.node_type === 'outcome_barrier') {\n            if (this.barrierDragMode === 'branch') {\n              this.handleBranchDragReorder(draggedNode);\n            } else if (this.barrierDragMode === 'barrier') {\n              this.handleBarrierDragReorder(draggedNode);\n            }\n          } else if (draggedNode && (draggedNode.node_type === 'threat' || draggedNode.node_type === 'outcome')) {\n            this.handleBranchDragReorder(draggedNode);\n          }\n\n          // Fast update connection wires\n          this.renderConnections();\n          this.updateHud(e);\n        }\n      }\n\n      // Live presence reporting to server (throttled)\n      if (this.viewport && this.app && typeof this.app.sendPresenceUpdate === 'function') {\n        const rect = this.viewport.getBoundingClientRect();\n        if (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom) {\n          const canvasX = (e.clientX - rect.left - this.panX) / this.scale;\n          const canvasY = (e.clientY - rect.top - this.panY) / this.scale;\n          this.app.sendPresenceUpdate(canvasX, canvasY, this.activeNodeId);\n        }\n      }\n    });\n\n    window.addEventListener('mouseup', () => {\n      if (this.isPanning) {\n        const wasPanning = this.isPanning;\n        const moved = this.panMoved;\n        this.isPanning = false;\n        this.viewport.classList.remove('panning');\n        if (wasPanning && !moved) {\n          this.setActiveNode(null);\n        }\n      }\n\n      if (this.isDraggingNode) {\n        const draggedNode = this.app.getNodeById(this.draggedNodeId);\n        const el = document.getElementById(`node-${this.draggedNodeId}`);\n        if (el) el.classList.remove('dragging');\n\n        const movedIds = Array.from(this.dragInitialPositions.keys());\n        const didDragMove = this.nodeDragMoved;\n\n        this.isDraggingNode = false;\n        this.nodeDragMoved = false;\n        this.draggedNodeId = null;\n        this.dragInitialPositions.clear();\n\n        if (!didDragMove) {\n          this.barrierDragState = null;\n          this.branchDragState = null;\n          this.barrierDragMode = null;\n          this.mitigationDragInitialOrder = null;\n          this.mitigationDragOrientation = null;\n          this.mitigationDragStartPos = null;\n          this.escalationFactorDragState = null;\n          this.dragInitialNodesSnapshot = null;\n          return;\n        }\n\n        if (draggedNode && draggedNode.node_type === 'mitigation') {\n          this.finalizeMitigationDragReorder(draggedNode);\n        } else if (draggedNode && draggedNode.node_type === 'escalation_factor') {\n          this.finalizeEscalationFactorDragReorder(draggedNode);\n        } else if (draggedNode && draggedNode.node_type === 'threat_barrier') {\n          this.finalizeBarrierDragReorder(draggedNode);\n        } else if (draggedNode && draggedNode.node_type === 'outcome_barrier') {\n          if (this.barrierDragMode === 'branch') {\n            this.barrierDragState = null;\n            this.finalizeBranchDragReorder(draggedNode);\n          } else if (this.barrierDragMode === 'barrier' || (!this.barrierDragMode && this.barrierDragState)) {\n            this.branchDragState = null;\n            this.finalizeBarrierDragReorder(draggedNode);\n          } else {\n            this.barrierDragState = null;\n            this.branchDragState = null;\n          }\n          this.barrierDragMode = null;\n        } else if (draggedNode && (draggedNode.node_type === 'threat' || draggedNode.node_type === 'outcome')) {\n          this.finalizeBranchDragReorder(draggedNode);\n        } else {\n          // Resolve collisions: push other non-moved nodes out of the way!\n          if (draggedNode && this.app.layout) {\n            const resolved = this.app.layout.resolveCollisions(draggedNode.diagram_id, movedIds);\n            if (resolved) {\n              this.renderConnections();\n            }\n          }\n\n          // Trigger auto-save\n          this.app.scheduleAutoSave('Moved elements in diagram hierarchy');\n        }\n\n        if (this.dragInitialNodesSnapshot) {\n          const diagId = this.dragInitialNodesSnapshot.diagramId;\n          const currentDiagNodes = (diagId && this.app.workspace && this.app.workspace.nodes)\n            ? this.app.workspace.nodes.filter(n => n.diagram_id === diagId)\n            : (this.app.workspace && this.app.workspace.nodes ? this.app.workspace.nodes : []);\n          if (JSON.stringify(currentDiagNodes) !== JSON.stringify(this.dragInitialNodesSnapshot.nodes)) {\n            this.app.pushSnapshotToUndo(this.dragInitialNodesSnapshot, 'Move elements');\n            if (this.app.autoAlignEnabled) {\n              this.app.triggerLineUpElements(true);\n            }\n          }\n          this.dragInitialNodesSnapshot = null;\n        }\n      }\n    });\n\n    // Zoom on wheel\n    this.viewport.addEventListener('wheel', (e) => {\n      e.preventDefault();\n      const zoomFactor = e.deltaY < 0 ? 1.08 : 0.92;\n      this.zoomAtPoint(e.clientX, e.clientY, zoomFactor);\n    }, { passive: false });\n\n    // Update HUD coordinates on hover\n    this.viewport.addEventListener('mousemove', (e) => {\n      this.updateHud(e);\n    });\n\n    this.viewport.addEventListener('mouseleave', () => {\n      this.updateHud();\n    });\n\n    window.addEventListener('resize', () => {\n      this.updateHud();\n    });\n\n    // Horizontal drag-to-scroll for canvas floating toolbar\n    const toolbar = document.querySelector('.canvas-floating-toolbar');\n    if (toolbar) {\n      let isToolbarDown = false;\n      let startX = 0;\n      let scrollLeft = 0;\n\n      toolbar.addEventListener('mousedown', (e) => {\n        isToolbarDown = true;\n        startX = e.pageX - toolbar.offsetLeft;\n        scrollLeft = toolbar.scrollLeft;\n      });\n\n      window.addEventListener('mouseup', () => {\n        isToolbarDown = false;\n      });\n\n      toolbar.addEventListener('mousemove', (e) => {\n        if (!isToolbarDown) return;\n        const x = e.pageX - toolbar.offsetLeft;\n        const walk = (x - startX);\n        toolbar.scrollLeft = scrollLeft - walk;\n      });\n    }\n\n    // Mini HUD click: toggle zoom on active element / zoom to fit\n    const hud = document.getElementById('canvasHud');\n    if (hud) {\n      hud.addEventListener('click', (e) => {\n        if (e.target.closest('#hudAutoZoomToggle')) return;\n        this.toggleHudZoom();\n      });\n    }\n\n    // Initialize multi-touch pan, pinch-to-zoom and touch dragging\n    this.initTouchEvents();\n  }\n\n  initTouchEvents() {\n    this.touchPinching = false;\n    this.initialPinchDistance = 0;\n    this.initialPinchScale = 1.0;\n    this.initialPinchCenter = { x: 0, y: 0 };\n\n    this.touchPanning = false;\n    this.touchPanStartX = 0;\n    this.touchPanStartY = 0;\n\n    this.touchDraggingNode = false;\n    this.touchDragStartX = 0;\n    this.touchDragStartY = 0;\n    this.touchMoved = false;\n\n    this.viewport.addEventListener('touchstart', (e) => {\n      // 1. Two-finger gesture: simultaneous pinch-to-zoom AND pan\n      if (e.touches.length === 2) {\n        e.preventDefault();\n        this.touchPanning = false;\n        this.touchDraggingNode = false;\n        if (this.draggedNodeId) {\n          const el = document.getElementById(`node-${this.draggedNodeId}`);\n          if (el) el.classList.remove('dragging');\n          this.draggedNodeId = null;\n          this.dragInitialPositions.clear();\n          this.mitigationDragInitialOrder = null;\n        }\n\n        const t1 = e.touches[0];\n        const t2 = e.touches[1];\n        const dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY);\n        const rect = this.viewport.getBoundingClientRect();\n        const midX = (t1.clientX + t2.clientX) / 2 - rect.left;\n        const midY = (t1.clientY + t2.clientY) / 2 - rect.top;\n\n        this.touchPinching = true;\n        this.lastPinchDist = Math.max(dist, 1);\n        this.lastPinchMidX = midX;\n        this.lastPinchMidY = midY;\n        return;\n      }\n\n      // 2. Single-finger touch\n      if (e.touches.length === 1) {\n        const touch = e.touches[0];\n        const target = e.target;\n\n        // If touching floating toolbar, situation runner HUD, or canvas HUD, let user interact without hijacking touch for canvas pan\n        if (target.closest('.canvas-floating-toolbar') || target.closest('.situation-runner-hud') || target.closest('.canvas-hud')) {\n          return;\n        }\n\n        // If touching textarea or button, let user interact with the form control\n        if (target.tagName === 'TEXTAREA' || target.closest('.node-btn')) {\n          const nodeEl = target.closest('.bowtie-node');\n          if (nodeEl) {\n            const nodeId = nodeEl.id.replace('node-', '');\n            this.setActiveNode(nodeId);\n          }\n          return;\n        }\n\n        const nodeEl = target.closest('.bowtie-node');\n        if (nodeEl) {\n          // Touch on node card: prepare for touch dragging\n          e.preventDefault();\n          const nodeId = nodeEl.id.replace('node-', '');\n          this.setActiveNode(nodeId);\n\n          if (!this.app.canEditCurrentDiagram()) {\n            this.touchDraggingNode = false;\n            this.draggedNodeId = null;\n            return;\n          }\n\n          this.touchDraggingNode = true;\n          this.draggedNodeId = nodeId;\n          this.touchDragStartX = touch.clientX;\n          this.touchDragStartY = touch.clientY;\n          this.touchMoved = false;\n          nodeEl.classList.add('dragging');\n          this.dragInitialNodesSnapshot = this.app.captureWorkspaceSnapshot();\n\n          // Capture initial positions of this node + all its branch/descendant elements\n          this.dragInitialPositions.clear();\n          const node = this.app.getNodeById(nodeId);\n          if (node) {\n            const branchIds = this.getBranchIdsForNode(node);\n            branchIds.forEach(bId => {\n              const bNode = this.app.getNodeById(bId);\n              if (bNode) {\n                this.dragInitialPositions.set(bId, { x: bNode.x, y: bNode.y });\n              }\n            });\n\n            if (node.node_type === 'mitigation') {\n              this.initMitigationDrag(node);\n            } else if (node.node_type === 'escalation_factor') {\n              this.initEscalationFactorDrag(node);\n            } else if (node.node_type === 'threat_barrier') {\n              this.initBarrierDrag(node);\n            } else if (node.node_type === 'outcome_barrier') {\n              this.initBranchDrag(node);\n              this.initBarrierDrag(node);\n              this.barrierDragMode = null;\n            } else if (node.node_type === 'threat' || node.node_type === 'outcome') {\n              this.initBranchDrag(node);\n            }\n          }\n        } else {\n          // Touch on empty canvas: dismiss mobile drawer if open and single-finger pan\n          if (window.innerWidth <= 900 && this.app.closeMobileDrawers) {\n            this.app.closeMobileDrawers();\n          }\n          if (this._zoomAnimId) {\n            cancelAnimationFrame(this._zoomAnimId);\n            this._zoomAnimId = null;\n          }\n          e.preventDefault();\n          this.touchPanning = true;\n          this.touchPanStartX = touch.clientX - this.panX;\n          this.touchPanStartY = touch.clientY - this.panY;\n          this.touchPanStartClientX = touch.clientX;\n          this.touchPanStartClientY = touch.clientY;\n          this.touchPanMoved = false;\n          this.viewport.classList.add('panning');\n        }\n      }\n    }, { passive: false });\n\n    this.viewport.addEventListener('touchmove', (e) => {\n      // Simultaneous two-finger pinch-to-zoom AND pan\n      if (this.touchPinching && e.touches.length === 2) {\n        e.preventDefault();\n        const t1 = e.touches[0];\n        const t2 = e.touches[1];\n        const dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY);\n        const rect = this.viewport.getBoundingClientRect();\n        const midX = (t1.clientX + t2.clientX) / 2 - rect.left;\n        const midY = (t1.clientY + t2.clientY) / 2 - rect.top;\n\n        // 1. Zoom calculation around previous midpoint\n        if (this.lastPinchDist > 0) {\n          const pinchFactor = dist / this.lastPinchDist;\n          const newScale = Math.min(Math.max(this.scale * pinchFactor, this.minScale), this.maxScale);\n          this.panX = this.lastPinchMidX - (this.lastPinchMidX - this.panX) * (newScale / this.scale);\n          this.panY = this.lastPinchMidY - (this.lastPinchMidY - this.panY) * (newScale / this.scale);\n          this.scale = newScale;\n        }\n\n        // 2. Pan calculation from midpoint displacement\n        const dPanX = midX - this.lastPinchMidX;\n        const dPanY = midY - this.lastPinchMidY;\n        this.panX += dPanX;\n        this.panY += dPanY;\n\n        this.lastPinchDist = Math.max(dist, 1);\n        this.lastPinchMidX = midX;\n        this.lastPinchMidY = midY;\n\n        this.updateTransform();\n        return;\n      }\n\n      // Dragging a node via touch\n      if (this.touchDraggingNode && this.draggedNodeId && e.touches.length === 1) {\n        if (this._zoomAnimId) {\n          cancelAnimationFrame(this._zoomAnimId);\n          this._zoomAnimId = null;\n        }\n        e.preventDefault();\n        const touch = e.touches[0];\n        const dx = (touch.clientX - this.touchDragStartX) / this.scale;\n        const dy = (touch.clientY - this.touchDragStartY) / this.scale;\n\n        if (!this.touchMoved) {\n          if (Math.hypot(dx, dy) > 4) {\n            this.touchMoved = true;\n          } else {\n            return;\n          }\n        }\n\n        const draggedNode = this.app.getNodeById(this.draggedNodeId);\n        if (draggedNode && draggedNode.node_type === 'outcome_barrier' && !this.barrierDragMode) {\n          const adx = Math.abs(dx);\n          const ady = Math.abs(dy);\n          if (adx > 8 || ady > 8) {\n            this.barrierDragMode = (ady > adx) ? 'branch' : 'barrier';\n          }\n        }\n\n        const isBarrierHorizontal = (\n          (draggedNode && draggedNode.node_type === 'threat_barrier') ||\n          (draggedNode && draggedNode.node_type === 'outcome_barrier' && this.barrierDragMode === 'barrier')\n        );\n        const barrierAttachedIds = isBarrierHorizontal && this.barrierDragState ?\n          new Set([draggedNode.id, ...(this.barrierDragState.subtrees.get(draggedNode.id) || []).map(it => it.id)]) : null;\n\n        for (const [id, initPos] of this.dragInitialPositions.entries()) {\n          if (barrierAttachedIds && !barrierAttachedIds.has(id)) {\n            continue;\n          }\n          const node = this.app.getNodeById(id);\n          if (node) {\n            node.x = Math.round(initPos.x + dx);\n            node.y = Math.round(initPos.y + dy);\n\n            const el = document.getElementById(`node-${id}`);\n            if (el) {\n              el.style.left = `${node.x}px`;\n              el.style.top = `${node.y}px`;\n            }\n          }\n        }\n\n        // Dynamic reordering & leveling for mitigation & barrier nodes\n        if (draggedNode && draggedNode.node_type === 'mitigation') {\n          this.handleMitigationDragReorder(draggedNode);\n        } else if (draggedNode && draggedNode.node_type === 'escalation_factor') {\n          this.handleEscalationFactorDragReorder(draggedNode);\n        } else if (draggedNode && draggedNode.node_type === 'threat_barrier') {\n          this.handleBarrierDragReorder(draggedNode);\n        } else if (draggedNode && draggedNode.node_type === 'outcome_barrier') {\n          if (this.barrierDragMode === 'branch') {\n            this.handleBranchDragReorder(draggedNode);\n          } else if (this.barrierDragMode === 'barrier') {\n            this.handleBarrierDragReorder(draggedNode);\n          }\n        } else if (draggedNode && (draggedNode.node_type === 'threat' || draggedNode.node_type === 'outcome')) {\n          this.handleBranchDragReorder(draggedNode);\n        }\n\n        this.renderConnections();\n        this.updateHud(touch);\n        return;\n      }\n\n      // Panning canvas via single touch\n      if (this.touchPanning && e.touches.length === 1) {\n        e.preventDefault();\n        const touch = e.touches[0];\n        if (Math.hypot(touch.clientX - this.touchPanStartClientX, touch.clientY - this.touchPanStartClientY) > 6) {\n          this.touchPanMoved = true;\n        }\n        this.panX = touch.clientX - this.touchPanStartX;\n        this.panY = touch.clientY - this.touchPanStartY;\n        this.updateTransform();\n      }\n    }, { passive: false });\n\n    const handleTouchEnd = (e) => {\n      // If one finger remains after two-finger gesture, smoothly transition to single-touch pan\n      if (this.touchPinching && e.touches && e.touches.length === 1) {\n        this.touchPinching = false;\n        const touch = e.touches[0];\n        this.touchPanning = true;\n        this.touchPanStartX = touch.clientX - this.panX;\n        this.touchPanStartY = touch.clientY - this.panY;\n        this.touchPanStartClientX = touch.clientX;\n        this.touchPanStartClientY = touch.clientY;\n        this.touchPanMoved = false;\n        this.viewport.classList.add('panning');\n        return;\n      }\n\n      if (this.touchPinching && (!e.touches || e.touches.length === 0)) {\n        this.touchPinching = false;\n      }\n\n      if (this.touchPanning && (!e.touches || e.touches.length === 0)) {\n        const wasTouchPanning = this.touchPanning;\n        const moved = this.touchPanMoved;\n        this.touchPanning = false;\n        this.viewport.classList.remove('panning');\n        if (wasTouchPanning && !moved) {\n          this.setActiveNode(null);\n        }\n      }\n\n      if (this.touchDraggingNode) {\n        const draggedNode = this.app.getNodeById(this.draggedNodeId);\n        const el = document.getElementById(`node-${this.draggedNodeId}`);\n        if (el) el.classList.remove('dragging');\n\n        const movedIds = Array.from(this.dragInitialPositions.keys());\n        const wasMoved = this.touchMoved;\n        const draggedId = this.draggedNodeId;\n\n        this.touchDraggingNode = false;\n        this.draggedNodeId = null;\n        this.dragInitialPositions.clear();\n\n        if (wasMoved && draggedNode) {\n          if (draggedNode.node_type === 'mitigation') {\n            this.finalizeMitigationDragReorder(draggedNode);\n          } else if (draggedNode.node_type === 'escalation_factor') {\n            this.finalizeEscalationFactorDragReorder(draggedNode);\n          } else if (draggedNode.node_type === 'threat_barrier') {\n            this.finalizeBarrierDragReorder(draggedNode);\n          } else if (draggedNode.node_type === 'outcome_barrier') {\n            if (this.barrierDragMode === 'branch') {\n              this.barrierDragState = null;\n              this.finalizeBranchDragReorder(draggedNode);\n            } else if (this.barrierDragMode === 'barrier' || (!this.barrierDragMode && this.barrierDragState)) {\n              this.branchDragState = null;\n              this.finalizeBarrierDragReorder(draggedNode);\n            } else {\n              this.barrierDragState = null;\n              this.branchDragState = null;\n            }\n            this.barrierDragMode = null;\n          } else if (draggedNode.node_type === 'threat' || draggedNode.node_type === 'outcome') {\n            this.finalizeBranchDragReorder(draggedNode);\n          } else {\n            if (this.app.layout) {\n              const resolved = this.app.layout.resolveCollisions(draggedNode.diagram_id, movedIds);\n              if (resolved) {\n                this.renderConnections();\n              }\n            }\n            this.app.scheduleAutoSave('Moved elements via touch');\n          }\n\n          if (this.dragInitialNodesSnapshot) {\n            const diagId = this.dragInitialNodesSnapshot.diagramId;\n            const currentDiagNodes = (diagId && this.app.workspace && this.app.workspace.nodes)\n              ? this.app.workspace.nodes.filter(n => n.diagram_id === diagId)\n              : (this.app.workspace && this.app.workspace.nodes ? this.app.workspace.nodes : []);\n            if (JSON.stringify(currentDiagNodes) !== JSON.stringify(this.dragInitialNodesSnapshot.nodes)) {\n              this.app.pushSnapshotToUndo(this.dragInitialNodesSnapshot, 'Move elements');\n              if (this.app.autoAlignEnabled) {\n                this.app.triggerLineUpElements(true);\n              }\n            }\n            this.dragInitialNodesSnapshot = null;\n          }\n        } else if (!wasMoved && draggedId) {\n          // Tap selection on node: activate node and open inspector drawer on mobile\n          this.setActiveNode(draggedId);\n          if (window.innerWidth <= 900) {\n            this.app.openMobileDrawer('right');\n          }\n        }\n        this.updateHud();\n      }\n    };\n\n    this.viewport.addEventListener('touchend', handleTouchEnd);\n    this.viewport.addEventListener('touchcancel', handleTouchEnd);\n  }\n\n  updateHud(e) {\n    const hudX = document.getElementById('hudX');\n    const hudY = document.getElementById('hudY');\n    const hudMag = document.getElementById('hudMag');\n\n    if (hudMag) {\n      hudMag.textContent = `${Math.round(this.scale * 100)}%`;\n    }\n\n    const rect = this.viewport.getBoundingClientRect();\n    if (!rect || rect.width === 0 || rect.height === 0) return;\n\n    let cx = 0;\n    let cy = 0;\n    if (e && typeof e.clientX === 'number') {\n      cx = Math.round((e.clientX - rect.left - this.panX) / this.scale);\n      cy = Math.round((e.clientY - rect.top - this.panY) / this.scale);\n    } else {\n      // Center of the viewing window in canvas coordinates\n      cx = Math.round((rect.width / 2 - this.panX) / this.scale);\n      cy = Math.round((rect.height / 2 - this.panY) / this.scale);\n    }\n\n    if (hudX) hudX.textContent = `${cx}`;\n    if (hudY) hudY.textContent = `${cy}`;\n\n    // Backwards-compatibility if canvasCoords is still present\n    const coordsEl = document.getElementById('canvasCoords');\n    if (coordsEl) {\n      coordsEl.textContent = `X: ${cx}, Y: ${cy}`;\n    }\n  }\n\n  zoomAtPoint(screenX, screenY, factor) {\n    const rect = this.viewport.getBoundingClientRect();\n    const mouseX = screenX - rect.left;\n    const mouseY = screenY - rect.top;\n\n    const newScale = Math.min(Math.max(this.scale * factor, this.minScale), this.maxScale);\n    if (newScale === this.scale) return;\n\n    // Keep point under cursor fixed\n    this.panX = mouseX - (mouseX - this.panX) * (newScale / this.scale);\n    this.panY = mouseY - (mouseY - this.panY) * (newScale / this.scale);\n    this.scale = newScale;\n\n    this.updateTransform();\n  }\n\n  setZoom(newScale) {\n    const rect = this.viewport.getBoundingClientRect();\n    const centerX = rect.width / 2;\n    const centerY = rect.height / 2;\n    const factor = newScale / this.scale;\n    this.zoomAtPoint(rect.left + centerX, rect.top + centerY, factor);\n  }\n\n  zoomIn() {\n    this.setZoom(this.scale * 1.15);\n  }\n\n  zoomOut() {\n    this.setZoom(this.scale * 0.85);\n  }\n\n  zoomReset() {\n    this.scale = 1.0;\n    this.panX = 60;\n    this.panY = 40;\n    this.updateTransform();\n  }\n\n  animateTransform(targetScale, targetPanX, targetPanY, duration = 280) {\n    if (this._zoomAnimId) {\n      cancelAnimationFrame(this._zoomAnimId);\n      this._zoomAnimId = null;\n    }\n\n    const startScale = this.scale;\n    const startPanX = this.panX;\n    const startPanY = this.panY;\n    const startTime = performance.now();\n\n    const step = (now) => {\n      const elapsed = now - startTime;\n      const progress = Math.min(elapsed / duration, 1);\n      const ease = 1 - Math.pow(1 - progress, 3);\n\n      this.scale = startScale + (targetScale - startScale) * ease;\n      this.panX = startPanX + (targetPanX - startPanX) * ease;\n      this.panY = startPanY + (targetPanY - startPanY) * ease;\n      this.updateTransform();\n\n      if (progress < 1) {\n        this._zoomAnimId = requestAnimationFrame(step);\n      } else {\n        this._zoomAnimId = null;\n      }\n    };\n\n    this._zoomAnimId = requestAnimationFrame(step);\n  }\n\n  // Compute safe viewport dimensions with a modest account for the top floating pill menu\n  getViewportInsets() {\n    const rect = this.viewport ? this.viewport.getBoundingClientRect() : null;\n    const vpWidth = (rect && rect.width > 50) ? rect.width : window.innerWidth;\n    const vpHeight = (rect && rect.height > 50) ? rect.height : (window.innerHeight - 100);\n\n    // Measure floating pill menu (.canvas-floating-toolbar)\n    let minTopEdge = 64;\n    let topClearance = 30; // modest vertical offset for scale calculation to account for pill menu\n\n    const toolbarEl = this.viewport \n      ? this.viewport.querySelector('.canvas-floating-toolbar') \n      : document.querySelector('.canvas-floating-toolbar');\n    if (toolbarEl && toolbarEl.offsetParent !== null) {\n      const tbRect = toolbarEl.getBoundingClientRect();\n      const vpTop = (rect && rect.top) ? rect.top : 0;\n      const toolbarBottomRelative = tbRect.bottom - vpTop;\n      if (toolbarBottomRelative > 0) {\n        minTopEdge = Math.round(toolbarBottomRelative + 8);\n        topClearance = Math.min(Math.round(toolbarBottomRelative / 2), 35);\n      }\n    }\n\n    return {\n      vpWidth,\n      vpHeight,\n      minTopEdge,\n      topClearance,\n    };\n  }\n\n  zoomFit(animate = false) {\n    this._lastHudToggle = 'fit';\n    if (!this.app.workspace || !this.app.workspace.nodes) {\n      this.zoomReset();\n      return;\n    }\n    const nodes = this.app.currentDiagram\n      ? this.app.workspace.nodes.filter(n => n.diagram_id === this.app.currentDiagram.id)\n      : [];\n    if (!nodes || nodes.length === 0) {\n      this.zoomReset();\n      return;\n    }\n\n    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n    nodes.forEach(n => {\n      minX = Math.min(minX, n.x);\n      minY = Math.min(minY, n.y);\n      maxX = Math.max(maxX, n.x + (n.width || 140));\n      maxY = Math.max(maxY, n.y + (n.height || 90));\n    });\n\n    const insets = this.getViewportInsets();\n    const vpWidth = insets.vpWidth;\n    const vpHeight = insets.vpHeight;\n\n    const paddingX = 80;\n    const paddingY = 80;\n    const contentW = Math.max(maxX - minX + paddingX, 200);\n    const contentH = Math.max(maxY - minY + paddingY, 150);\n\n    // Revert to previous zoom calculation, with only a small account for the top pill menu on height\n    const scaleX = vpWidth / contentW;\n    const scaleY = (vpHeight - insets.topClearance) / contentH;\n    const fitScale = Math.min(scaleX, scaleY);\n    const targetScale = Math.min(Math.max(fitScale, this.minScale), 1.25);\n\n    const contentCenterX = (minX + maxX) / 2;\n    const contentCenterY = (minY + maxY) / 2;\n    const targetPanX = vpWidth / 2 - contentCenterX * targetScale;\n    let targetPanY = (vpHeight / 2 + Math.round(insets.topClearance / 2)) - contentCenterY * targetScale;\n\n    // Safety check: ensure topmost content is not placed behind the floating pill menu\n    const screenMinY = targetPanY + minY * targetScale;\n    if (screenMinY < insets.minTopEdge) {\n      targetPanY += (insets.minTopEdge - screenMinY);\n    }\n\n    if (animate) {\n      this.animateTransform(targetScale, targetPanX, targetPanY);\n    } else {\n      if (this._zoomAnimId) {\n        cancelAnimationFrame(this._zoomAnimId);\n        this._zoomAnimId = null;\n      }\n      this.scale = targetScale;\n      this.panX = targetPanX;\n      this.panY = targetPanY;\n      this.updateTransform();\n    }\n  }\n\n  zoomOnActiveElement(animate = true) {\n    this._lastHudToggle = 'active';\n    let node = this.activeNodeId ? this.app.getNodeById(this.activeNodeId) : null;\n    if (!node && this.app.workspace && this.app.workspace.nodes) {\n      const currentNodes = this.app.currentDiagram\n        ? this.app.workspace.nodes.filter(n => n.diagram_id === this.app.currentDiagram.id)\n        : [];\n      node = currentNodes.find(n => n.node_type === 'top_event') || currentNodes[0];\n    }\n    if (!node) return;\n\n    const insets = this.getViewportInsets();\n    const vpWidth = insets.vpWidth;\n    const vpHeight = insets.vpHeight;\n\n    const targetScale = 1.15;\n    const targetX = node.x + (node.width || 140) / 2;\n    const targetY = node.y + (node.height || 90) / 2;\n\n    const targetPanX = vpWidth / 2 - targetX * targetScale;\n    let targetPanY = vpHeight / 2 - targetY * targetScale;\n\n    // Ensure focused node's top is not obscured behind pill menu\n    const nodeTopScreenY = targetPanY + node.y * targetScale;\n    if (nodeTopScreenY < insets.minTopEdge) {\n      targetPanY += (insets.minTopEdge - nodeTopScreenY);\n    }\n\n    if (animate) {\n      this.animateTransform(targetScale, targetPanX, targetPanY);\n    } else {\n      if (this._zoomAnimId) {\n        cancelAnimationFrame(this._zoomAnimId);\n        this._zoomAnimId = null;\n      }\n      this.scale = targetScale;\n      this.panX = targetPanX;\n      this.panY = targetPanY;\n      this.updateTransform();\n    }\n  }\n\n  toggleHudZoom() {\n    if (this._lastHudToggle === 'active') {\n      this.zoomFit(true);\n    } else {\n      this.zoomOnActiveElement(true);\n    }\n  }\n\n  zoomFitAndCenterTopEvent(topEventId = null) {\n    if (!this.app.workspace || !this.app.workspace.nodes) {\n      this.zoomReset();\n      return;\n    }\n    const nodes = this.app.currentDiagram\n      ? this.app.workspace.nodes.filter(n => n.diagram_id === this.app.currentDiagram.id)\n      : [];\n    if (!nodes || nodes.length === 0) {\n      this.zoomReset();\n      return;\n    }\n\n    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n    nodes.forEach(n => {\n      minX = Math.min(minX, n.x);\n      minY = Math.min(minY, n.y);\n      maxX = Math.max(maxX, n.x + (n.width || 140));\n      maxY = Math.max(maxY, n.y + (n.height || 90));\n    });\n\n    const insets = this.getViewportInsets();\n    const vpWidth = insets.vpWidth;\n    const vpHeight = insets.vpHeight;\n\n    const paddingX = 80;\n    const paddingY = 80;\n    const contentW = Math.max(maxX - minX + paddingX, 200);\n    const contentH = Math.max(maxY - minY + paddingY, 150);\n\n    // Revert to previous zoom calculation with small account for pill menu\n    const scaleX = vpWidth / contentW;\n    const scaleY = (vpHeight - insets.topClearance) / contentH;\n    const fitScale = Math.min(scaleX, scaleY);\n    this.scale = Math.min(Math.max(fitScale, this.minScale), 1.15);\n\n    // Locate the Top Event node\n    const topEventNode = topEventId \n      ? this.app.getNodeById(topEventId) \n      : (nodes.find(n => n.node_type === 'top_event') || nodes[0]);\n\n    if (topEventNode) {\n      const targetX = topEventNode.x + (topEventNode.width || 130) / 2;\n      const targetY = topEventNode.y + (topEventNode.height || 130) / 2;\n      let idealPanX = vpWidth / 2 - targetX * this.scale;\n      let idealPanY = (vpHeight / 2 + Math.round(insets.topClearance / 2)) - targetY * this.scale;\n\n      // Ensure the left edge of content (Threats / Hazards) is not pushed off screen if diagram is wide\n      const leftEdgeScreenX = idealPanX + minX * this.scale;\n      if (leftEdgeScreenX < 20) {\n        idealPanX += (20 - leftEdgeScreenX);\n      }\n\n      // Ensure the top edge of content is never behind the floating pill menu\n      const topEdgeScreenY = idealPanY + minY * this.scale;\n      if (topEdgeScreenY < insets.minTopEdge) {\n        idealPanY += (insets.minTopEdge - topEdgeScreenY);\n      }\n\n      this.panX = idealPanX;\n      this.panY = idealPanY;\n    } else {\n      const contentCenterX = (minX + maxX) / 2;\n      const contentCenterY = (minY + maxY) / 2;\n      let idealPanX = vpWidth / 2 - contentCenterX * this.scale;\n      let idealPanY = (vpHeight / 2 + Math.round(insets.topClearance / 2)) - contentCenterY * this.scale;\n\n      const topEdgeScreenY = idealPanY + minY * this.scale;\n      if (topEdgeScreenY < insets.minTopEdge) {\n        idealPanY += (insets.minTopEdge - topEdgeScreenY);\n      }\n\n      this.panX = idealPanX;\n      this.panY = idealPanY;\n    }\n\n    this.updateTransform();\n  }\n\n  centerOnNode(nodeId) {\n    const node = this.app.getNodeById(nodeId);\n    if (!node) return;\n    const insets = this.getViewportInsets();\n    const targetX = node.x + (node.width || 140) / 2;\n    const targetY = node.y + (node.height || 90) / 2;\n\n    let targetPanX = insets.vpWidth / 2 - targetX * this.scale;\n    let targetPanY = insets.vpHeight / 2 - targetY * this.scale;\n\n    const nodeTopScreenY = targetPanY + node.y * this.scale;\n    if (nodeTopScreenY < insets.minTopEdge) {\n      targetPanY += (insets.minTopEdge - nodeTopScreenY);\n    }\n\n    this.panX = targetPanX;\n    this.panY = targetPanY;\n    this.updateTransform();\n  }\n\n  centerOnCoordinates(targetX, targetY) {\n    const insets = this.getViewportInsets();\n    this.panX = insets.vpWidth / 2 - targetX * this.scale;\n    this.panY = insets.vpHeight / 2 - targetY * this.scale;\n    this.updateTransform();\n  }\n\n  updateTransform() {\n    this.stage.style.transform = `translate(${this.panX}px, ${this.panY}px) scale(${this.scale})`;\n    const zoomDisplay = document.getElementById('zoomDisplay');\n    const mobileZoomDisplay = document.getElementById('mobileZoomDisplay');\n    const zoomText = `${Math.round(this.scale * 100)}%`;\n    if (zoomDisplay) {\n      zoomDisplay.textContent = zoomText;\n    }\n    if (mobileZoomDisplay) {\n      mobileZoomDisplay.textContent = zoomText;\n    }\n    this.updateHud();\n  }\n\n  // Initialize baseline order and orientation for sibling mitigations prior to drag\n  initMitigationDrag(node) {\n    if (!node || node.node_type !== 'mitigation') return;\n    const ef = this.app.getNodeById(node.parent_id);\n    if (!ef) return;\n    const diagNodes = this.app.workspace.nodes.filter(n => n.diagram_id === node.diagram_id);\n    const siblings = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n    if (siblings.length <= 1) {\n      this.mitigationDragInitialOrder = [node];\n      this.mitigationDragOrientation = 'vertical';\n      this.mitigationDragStartPos = { x: node.x, y: node.y };\n      return;\n    }\n    const barrier = this.app.getNodeById(ef.parent_id);\n    const topEvent = diagNodes.find(n => n.node_type === 'top_event');\n    const isThreatSide = barrier \n      ? (barrier.node_type === 'threat_barrier' || (topEvent && barrier.x < topEvent.x))\n      : (topEvent && ef.x < topEvent.x);\n\n    const ySpread = Math.max(...siblings.map(n => n.y)) - Math.min(...siblings.map(n => n.y));\n    const xSpread = Math.max(...siblings.map(n => n.x)) - Math.min(...siblings.map(n => n.x));\n    const isCurrentlyVertical = ySpread > 30 || (ySpread >= xSpread && ySpread > 10);\n\n    this.mitigationDragOrientation = isCurrentlyVertical ? 'vertical' : 'horizontal';\n    this.mitigationDragInitialOrder = [...siblings].sort((a, b) => {\n      if (isCurrentlyVertical) {\n        return a.y - b.y;\n      } else {\n        return isThreatSide ? (b.x - a.x) : (a.x - b.x);\n      }\n    });\n    this.mitigationDragStartPos = { x: node.x, y: node.y };\n  }\n\n  // Dynamically shift sibling mitigations to open up the slot for the dragged card (supports vertical & horizontal sorting)\n  handleMitigationDragReorder(draggedNode) {\n    if (!draggedNode || draggedNode.node_type !== 'mitigation') return;\n    const ef = this.app.getNodeById(draggedNode.parent_id);\n    if (!ef) return;\n\n    const diagNodes = this.app.workspace.nodes.filter(n => n.diagram_id === draggedNode.diagram_id);\n    const siblings = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n    if (siblings.length <= 1) return;\n\n    const barrier = this.app.getNodeById(ef.parent_id);\n    const topEvent = diagNodes.find(n => n.node_type === 'top_event');\n    const isThreatSide = barrier \n      ? (barrier.node_type === 'threat_barrier' || (topEvent && barrier.x < topEvent.x))\n      : (topEvent && ef.x < topEvent.x);\n\n    const startPos = this.mitigationDragStartPos || { x: draggedNode.x, y: draggedNode.y };\n    const deltaX = draggedNode.x - startPos.x;\n    const deltaY = draggedNode.y - startPos.y;\n\n    // Detect user intention: vertical vs horizontal\n    let orientation = this.mitigationDragOrientation || 'vertical';\n    if (Math.abs(deltaY) > 25 && Math.abs(deltaY) > Math.abs(deltaX) * 1.2) {\n      orientation = 'vertical';\n      this.mitigationDragOrientation = 'vertical';\n    } else if (Math.abs(deltaX) > 35 && Math.abs(deltaX) > Math.abs(deltaY) * 1.2) {\n      orientation = 'horizontal';\n      this.mitigationDragOrientation = 'horizontal';\n    }\n\n    let initialOrder = this.mitigationDragInitialOrder;\n    if (!initialOrder || initialOrder.length !== siblings.length) {\n      initialOrder = [...siblings].sort((a, b) => {\n        return orientation === 'vertical' ? (a.y - b.y) : (isThreatSide ? (b.x - a.x) : (a.x - b.x));\n      });\n      this.mitigationDragInitialOrder = initialOrder;\n    }\n\n    const mitW = draggedNode.width || 120;\n    const mitH = draggedNode.height || 75;\n    const efW = ef.width || 130;\n    const gap = 25;\n    const stepX = mitW + gap;\n    const stepY = mitH + 20;\n\n    const slot0X = isThreatSide ? (ef.x - gap - mitW) : (ef.x + efW + gap);\n    const slot0Y = ef.y;\n\n    let targetSlot;\n    if (orientation === 'vertical') {\n      const rawSlot = (draggedNode.y - slot0Y) / stepY;\n      targetSlot = Math.max(0, Math.min(siblings.length - 1, Math.round(rawSlot)));\n    } else {\n      const rawSlot = isThreatSide \n        ? (slot0X - draggedNode.x) / stepX \n        : (draggedNode.x - slot0X) / stepX;\n      targetSlot = Math.max(0, Math.min(siblings.length - 1, Math.round(rawSlot)));\n    }\n\n    const remaining = initialOrder.filter(n => n.id !== draggedNode.id);\n    const newOrder = [\n      ...remaining.slice(0, targetSlot),\n      draggedNode,\n      ...remaining.slice(targetSlot)\n    ];\n\n    newOrder.forEach((sib, slotIdx) => {\n      if (sib.id === draggedNode.id) return;\n      if (orientation === 'vertical') {\n        sib.x = slot0X;\n        sib.y = slot0Y + slotIdx * stepY;\n      } else {\n        sib.x = isThreatSide ? (slot0X - slotIdx * stepX) : (slot0X + slotIdx * stepX);\n        sib.y = ef.y;\n      }\n      const el = document.getElementById(`node-${sib.id}`);\n      if (el) {\n        el.style.left = `${Math.round(sib.x)}px`;\n        el.style.top = `${Math.round(sib.y)}px`;\n      }\n    });\n  }\n\n  // Snap all sibling mitigations to their canonical slots (vertical or horizontal) on drop\n  finalizeMitigationDragReorder(draggedNode) {\n    if (!draggedNode || draggedNode.node_type !== 'mitigation') return;\n    const ef = this.app.getNodeById(draggedNode.parent_id);\n    if (!ef) return;\n\n    const diagNodes = this.app.workspace.nodes.filter(n => n.diagram_id === draggedNode.diagram_id);\n    const siblings = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n    if (siblings.length === 0) return;\n\n    const barrier = this.app.getNodeById(ef.parent_id);\n    const topEvent = diagNodes.find(n => n.node_type === 'top_event');\n    const isThreatSide = barrier \n      ? (barrier.node_type === 'threat_barrier' || (topEvent && barrier.x < topEvent.x))\n      : (topEvent && ef.x < topEvent.x);\n\n    const orientation = this.mitigationDragOrientation || 'vertical';\n    const mitW = draggedNode.width || 120;\n    const mitH = draggedNode.height || 75;\n    const efW = ef.width || 130;\n    const gap = 25;\n    const stepX = mitW + gap;\n    const stepY = mitH + 20;\n\n    const slot0X = isThreatSide ? (ef.x - gap - mitW) : (ef.x + efW + gap);\n    const slot0Y = ef.y;\n\n    if (siblings.length === 1) {\n      draggedNode.x = slot0X;\n      draggedNode.y = slot0Y;\n      const el = document.getElementById(`node-${draggedNode.id}`);\n      if (el) {\n        el.style.left = `${Math.round(draggedNode.x)}px`;\n        el.style.top = `${Math.round(draggedNode.y)}px`;\n      }\n      this.mitigationDragInitialOrder = null;\n      this.mitigationDragOrientation = null;\n      this.mitigationDragStartPos = null;\n      this.renderConnections();\n      this.app.scheduleAutoSave('Positioned mitigation');\n      return;\n    }\n\n    let initialOrder = this.mitigationDragInitialOrder;\n    if (!initialOrder || initialOrder.length !== siblings.length) {\n      initialOrder = [...siblings].sort((a, b) => {\n        return orientation === 'vertical' ? (a.y - b.y) : (isThreatSide ? (b.x - a.x) : (a.x - b.x));\n      });\n    }\n\n    let targetSlot;\n    if (orientation === 'vertical') {\n      const rawSlot = (draggedNode.y - slot0Y) / stepY;\n      targetSlot = Math.max(0, Math.min(siblings.length - 1, Math.round(rawSlot)));\n    } else {\n      const rawSlot = isThreatSide \n        ? (slot0X - draggedNode.x) / stepX \n        : (draggedNode.x - slot0X) / stepX;\n      targetSlot = Math.max(0, Math.min(siblings.length - 1, Math.round(rawSlot)));\n    }\n\n    const remaining = initialOrder.filter(n => n.id !== draggedNode.id);\n    const newOrder = [\n      ...remaining.slice(0, targetSlot),\n      draggedNode,\n      ...remaining.slice(targetSlot)\n    ];\n\n    // Snap all siblings (including dragged card) to exact slot positions\n    newOrder.forEach((sib, slotIdx) => {\n      if (orientation === 'vertical') {\n        sib.x = slot0X;\n        sib.y = slot0Y + slotIdx * stepY;\n      } else {\n        sib.x = isThreatSide ? (slot0X - slotIdx * stepX) : (slot0X + slotIdx * stepX);\n        sib.y = ef.y;\n      }\n      const el = document.getElementById(`node-${sib.id}`);\n      if (el) {\n        el.style.left = `${Math.round(sib.x)}px`;\n        el.style.top = `${Math.round(sib.y)}px`;\n      }\n    });\n\n    const orderChanged = newOrder.some((n, i) => n.id !== initialOrder[i].id);\n    if (!orderChanged) {\n      this.mitigationDragInitialOrder = null;\n      this.mitigationDragOrientation = null;\n      this.mitigationDragStartPos = null;\n      this.renderConnections();\n      return;\n    }\n\n    // Reorder siblings in workspace.nodes to reflect new order\n    const siblingIds = new Set(newOrder.map(n => n.id));\n    const firstSibIdx = this.app.workspace.nodes.findIndex(n => siblingIds.has(n.id));\n    if (firstSibIdx !== -1) {\n      const before = this.app.workspace.nodes.slice(0, firstSibIdx).filter(n => !siblingIds.has(n.id));\n      const after = this.app.workspace.nodes.slice(firstSibIdx).filter(n => !siblingIds.has(n.id));\n      this.app.workspace.nodes = [...before, ...newOrder, ...after];\n    }\n\n    this.mitigationDragInitialOrder = null;\n    this.mitigationDragOrientation = null;\n    this.mitigationDragStartPos = null;\n    this.renderConnections();\n    if (this.app.tree) {\n      this.app.tree.render();\n    }\n    this.app.scheduleAutoSave('Reordered mitigations');\n  }\n\n  // Initialize baseline order and slots for sibling escalation factors under a barrier prior to drag\n  initEscalationFactorDrag(node) {\n    if (!node || node.node_type !== 'escalation_factor') return;\n    const barrier = this.app.getNodeById(node.parent_id);\n    if (!barrier) return;\n\n    const diagNodes = this.app.workspace.nodes.filter(n => n.diagram_id === node.diagram_id);\n    const siblings = diagNodes.filter(n => n.parent_id === barrier.id && n.node_type === 'escalation_factor');\n    const slot0X = Math.round(barrier.x + ((barrier.width || 135) - 130) / 2);\n\n    if (siblings.length <= 1) {\n      this.escalationFactorDragState = {\n        draggedEf: node,\n        barrier,\n        initialOrder: [node],\n        slots: [Math.round(node.y)],\n        subtrees: new Map(),\n        slot0X,\n        startPos: { x: node.x, y: node.y },\n      };\n      return;\n    }\n\n    // Sort siblings strictly vertically by initial Y\n    const initialOrder = [...siblings].sort((a, b) => (a.y || 0) - (b.y || 0));\n\n    // Helper to calculate total height of an escalation factor (including vertical mitigations if any)\n    const getEfHeight = (ef) => {\n      const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n      const ySpread = mits.length > 1 ? (Math.max(...mits.map(n => n.y)) - Math.min(...mits.map(n => n.y))) : 0;\n      const xSpread = mits.length > 1 ? (Math.max(...mits.map(n => n.x)) - Math.min(...mits.map(n => n.x))) : 0;\n      const isVert = mits.length > 1 && (ySpread > 20 || ySpread >= xSpread);\n      return isVert ? Math.max(98, (mits.length * 95) + 15) : 98;\n    };\n\n    // Calculate baseline vertical slots for each escalation factor\n    const slots = [];\n    let currSlotY = Math.min(...initialOrder.map(ef => Math.round(ef.y)));\n    const barrierBaseY = Math.round(barrier.y + (barrier.height || 80) + 25);\n    if (isNaN(currSlotY) || currSlotY < barrierBaseY - 20) {\n      currSlotY = barrierBaseY;\n    }\n\n    initialOrder.forEach((ef, idx) => {\n      if (idx === 0) {\n        slots.push(currSlotY);\n      } else {\n        const prevEf = initialOrder[idx - 1];\n        currSlotY += getEfHeight(prevEf);\n        slots.push(currSlotY);\n      }\n    });\n\n    // Capture child mitigation subtrees relative to each escalation factor\n    const subtrees = new Map();\n    initialOrder.forEach(ef => {\n      const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n      subtrees.set(ef.id, mits.map(mit => ({\n        id: mit.id,\n        relX: mit.x - ef.x,\n        relY: mit.y - ef.y,\n      })));\n    });\n\n    this.escalationFactorDragState = {\n      draggedEf: node,\n      barrier,\n      initialOrder,\n      slots,\n      subtrees,\n      slot0X,\n      startPos: { x: node.x, y: node.y },\n    };\n  }\n\n  // Dynamically shift sibling escalation factors to open up slot for dragged card\n  handleEscalationFactorDragReorder(draggedNode) {\n    if (!this.escalationFactorDragState || this.escalationFactorDragState.draggedEf.id !== draggedNode.id) {\n      return;\n    }\n\n    const { barrier, initialOrder, slots, subtrees, slot0X } = this.escalationFactorDragState;\n\n    // 1. Lock dragged EF horizontally under the barrier\n    draggedNode.x = slot0X;\n    const draggedEl = document.getElementById(`node-${draggedNode.id}`);\n    if (draggedEl) {\n      draggedEl.style.left = `${Math.round(slot0X)}px`;\n    }\n\n    // 2. Keep dragged EF's child mitigations moving along with it\n    const draggedSubtree = subtrees.get(draggedNode.id) || [];\n    draggedSubtree.forEach(item => {\n      const mNode = this.app.getNodeById(item.id);\n      if (mNode) {\n        mNode.x = Math.round(slot0X + item.relX);\n        mNode.y = Math.round(draggedNode.y + item.relY);\n        const mEl = document.getElementById(`node-${mNode.id}`);\n        if (mEl) {\n          mEl.style.left = `${mNode.x}px`;\n          mEl.style.top = `${mNode.y}px`;\n        }\n      }\n    });\n\n    if (initialOrder.length <= 1) return;\n\n    // 3. Determine target vertical slot based on draggedNode.y relative to slot midpoints\n    let targetSlot = 0;\n    while (\n      targetSlot < slots.length - 1 &&\n      draggedNode.y > (slots[targetSlot] + slots[targetSlot + 1]) / 2\n    ) {\n      targetSlot++;\n    }\n\n    // 4. Preview order with draggedNode in targetSlot\n    const remaining = initialOrder.filter(n => n.id !== draggedNode.id);\n    const previewOrder = [\n      ...remaining.slice(0, targetSlot),\n      draggedNode,\n      ...remaining.slice(targetSlot)\n    ];\n\n    // 5. Shift other sibling EFs to their preview slot positions\n    previewOrder.forEach((ef, slotIdx) => {\n      if (ef.id === draggedNode.id) return;\n      const targetY = slots[slotIdx];\n      ef.x = slot0X;\n      ef.y = targetY;\n\n      const efEl = document.getElementById(`node-${ef.id}`);\n      if (efEl) {\n        efEl.style.left = `${Math.round(ef.x)}px`;\n        efEl.style.top = `${Math.round(ef.y)}px`;\n      }\n\n      // Shift this EF's child mitigations with it\n      const mitSubtree = subtrees.get(ef.id) || [];\n      mitSubtree.forEach(item => {\n        const mNode = this.app.getNodeById(item.id);\n        if (mNode) {\n          mNode.x = Math.round(slot0X + item.relX);\n          mNode.y = Math.round(targetY + item.relY);\n          const mEl = document.getElementById(`node-${mNode.id}`);\n          if (mEl) {\n            mEl.style.left = `${mNode.x}px`;\n            mEl.style.top = `${mNode.y}px`;\n          }\n        }\n      });\n    });\n  }\n\n  // Snap all sibling escalation factors into canonical slots and update workspace ordering\n  finalizeEscalationFactorDragReorder(draggedNode) {\n    if (!this.escalationFactorDragState) return;\n\n    const { barrier, initialOrder, slots, subtrees, slot0X } = this.escalationFactorDragState;\n\n    if (initialOrder.length <= 1) {\n      draggedNode.x = slot0X;\n      draggedNode.y = slots[0] || draggedNode.y;\n      const draggedEl = document.getElementById(`node-${draggedNode.id}`);\n      if (draggedEl) {\n        draggedEl.style.left = `${Math.round(draggedNode.x)}px`;\n        draggedEl.style.top = `${Math.round(draggedNode.y)}px`;\n      }\n      const draggedSubtree = subtrees.get(draggedNode.id) || [];\n      draggedSubtree.forEach(item => {\n        const mNode = this.app.getNodeById(item.id);\n        if (mNode) {\n          mNode.x = Math.round(slot0X + item.relX);\n          mNode.y = Math.round(draggedNode.y + item.relY);\n          const mEl = document.getElementById(`node-${mNode.id}`);\n          if (mEl) {\n            mEl.style.left = `${mNode.x}px`;\n            mEl.style.top = `${mNode.y}px`;\n          }\n        }\n      });\n      this.escalationFactorDragState = null;\n      this.renderConnections();\n      this.app.scheduleAutoSave('Positioned escalation factor');\n      return;\n    }\n\n    // Determine target slot\n    let targetSlot = 0;\n    while (\n      targetSlot < slots.length - 1 &&\n      draggedNode.y > (slots[targetSlot] + slots[targetSlot + 1]) / 2\n    ) {\n      targetSlot++;\n    }\n\n    const remaining = initialOrder.filter(n => n.id !== draggedNode.id);\n    const finalOrder = [\n      ...remaining.slice(0, targetSlot),\n      draggedNode,\n      ...remaining.slice(targetSlot)\n    ];\n\n    // Snap all EFs and their child mitigations to final slots\n    finalOrder.forEach((ef, slotIdx) => {\n      ef.x = slot0X;\n      ef.y = slots[slotIdx];\n\n      const efEl = document.getElementById(`node-${ef.id}`);\n      if (efEl) {\n        efEl.style.left = `${Math.round(ef.x)}px`;\n        efEl.style.top = `${Math.round(ef.y)}px`;\n      }\n\n      const mitSubtree = subtrees.get(ef.id) || [];\n      mitSubtree.forEach(item => {\n        const mNode = this.app.getNodeById(item.id);\n        if (mNode) {\n          mNode.x = Math.round(slot0X + item.relX);\n          mNode.y = Math.round(ef.y + item.relY);\n          const mEl = document.getElementById(`node-${mNode.id}`);\n          if (mEl) {\n            mEl.style.left = `${mNode.x}px`;\n            mEl.style.top = `${mNode.y}px`;\n          }\n        }\n      });\n    });\n\n    const orderChanged = finalOrder.some((n, i) => n.id !== initialOrder[i].id);\n    if (!orderChanged) {\n      this.escalationFactorDragState = null;\n      this.renderConnections();\n      return;\n    }\n\n    // Reorder in workspace.nodes to maintain consistent array order\n    const efIds = new Set(finalOrder.map(e => e.id));\n    const firstIdx = this.app.workspace.nodes.findIndex(n => efIds.has(n.id));\n    if (firstIdx !== -1) {\n      const before = this.app.workspace.nodes.slice(0, firstIdx).filter(n => !efIds.has(n.id));\n      const after = this.app.workspace.nodes.slice(firstIdx).filter(n => !efIds.has(n.id));\n      this.app.workspace.nodes = [...before, ...finalOrder, ...after];\n    }\n\n    this.escalationFactorDragState = null;\n    this.renderConnections();\n    if (this.app.tree) this.app.tree.render();\n    this.app.scheduleAutoSave('Reordered escalation factors');\n\n    if (this.app.autoAlignEnabled) {\n      this.app.triggerLineUpElements(true);\n    }\n  }\n\n  // Get all items in a barrier's attached subtree (Escalation Factors and Mitigations ONLY)\n  getBarrierSubtreeItems(barrier, diagNodes) {\n    if (!barrier) return [];\n    const items = [];\n    const efs = diagNodes\n      .filter(n => n.parent_id === barrier.id && n.node_type === 'escalation_factor')\n      .sort((a, b) => (a.y || 0) - (b.y || 0));\n    efs.forEach(ef => {\n      items.push({\n        id: ef.id,\n        relX: ef.x - barrier.x,\n        relY: ef.y - barrier.y,\n      });\n      const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n      mits.forEach(mit => {\n        items.push({\n          id: mit.id,\n          relX: mit.x - barrier.x,\n          relY: mit.y - barrier.y,\n        });\n      });\n    });\n    return items;\n  }\n\n  // Initialize barrier drag reordering along the branch\n  initBarrierDrag(barrierNode) {\n    if (!barrierNode || (barrierNode.node_type !== 'threat_barrier' && barrierNode.node_type !== 'outcome_barrier')) {\n      this.barrierDragState = null;\n      return;\n    }\n\n    const diagNodes = this.app.workspace.nodes.filter(n => n.diagram_id === barrierNode.diagram_id);\n    const topEvent = this.app.layout\n      ? this.app.layout.getTopEventForNode(barrierNode, diagNodes)\n      : diagNodes.find(n => n.node_type === 'top_event');\n    const isThreat = barrierNode.node_type === 'threat_barrier';\n\n    let branchBarriers = [];\n    let branchY = barrierNode.y;\n    let branchRoot = null;\n    let outcome = null;\n    let threat = null;\n\n    if (isThreat) {\n      const threats = diagNodes.filter(n => n.node_type === 'threat');\n      for (const t of threats) {\n        const chain = this.app.layout ? this.app.layout.getLinearChainForThreat(t, diagNodes) : [];\n        if (chain.some(b => b.id === barrierNode.id)) {\n          threat = t;\n          branchBarriers = chain;\n          branchY = t.y;\n          branchRoot = t;\n          break;\n        }\n      }\n      if (!threat) {\n        branchBarriers = diagNodes.filter(n => n.node_type === 'threat_barrier' && Math.abs(n.y - barrierNode.y) < 50);\n      }\n    } else {\n      // Outcome branch: find right branch chain\n      const rightBranches = (this.app.layout && topEvent)\n        ? this.app.layout.getClusterRightBranches(topEvent, diagNodes)\n        : [];\n      for (const branch of rightBranches) {\n        if (branch.chain.some(b => b.id === barrierNode.id)) {\n          branchBarriers = branch.chain.filter(n => n.node_type === 'outcome_barrier');\n          outcome = branch.outcome || branch.chain.find(n => n.node_type === 'outcome');\n          branchRoot = topEvent || branch.root;\n          branchY = outcome ? outcome.y : (branch.root.y || barrierNode.y);\n          break;\n        }\n      }\n      if (branchBarriers.length === 0) {\n        branchBarriers = diagNodes.filter(n => n.node_type === 'outcome_barrier' && Math.abs(n.y - barrierNode.y) < 60);\n        outcome = diagNodes.find(n => n.node_type === 'outcome' && Math.abs(n.y - barrierNode.y) < 60);\n        branchY = outcome ? outcome.y : barrierNode.y;\n        branchRoot = topEvent;\n      }\n    }\n\n    if (branchBarriers.length === 0) {\n      branchBarriers = [barrierNode];\n    }\n\n    // Sort barriers horizontally along the branch (left-to-right)\n    const initialBarriers = [...branchBarriers].sort((a, b) => a.x - b.x);\n\n    // Capture initial slot X positions\n    const slots = initialBarriers.map(b => Math.round(b.x));\n    for (let i = 1; i < slots.length; i++) {\n      if (slots[i] < slots[i - 1] + 175) {\n        slots[i] = slots[i - 1] + 175;\n      }\n    }\n\n    // Capture subtree offsets for each barrier (Escalation Factors and Mitigations under each barrier ONLY)\n    const subtrees = new Map();\n    initialBarriers.forEach(b => {\n      subtrees.set(b.id, this.getBarrierSubtreeItems(b, diagNodes));\n    });\n\n    this.barrierDragState = {\n      draggedBarrier: barrierNode,\n      branchType: isThreat ? 'threat' : 'outcome',\n      branchY,\n      branchRoot,\n      threat,\n      outcome,\n      initialBarriers,\n      slots,\n      subtrees,\n    };\n  }\n\n  // Dynamically shift sibling barriers to open up slot for dragged barrier card\n  handleBarrierDragReorder(draggedNode) {\n    if (!this.barrierDragState || this.barrierDragState.draggedBarrier.id !== draggedNode.id) {\n      return;\n    }\n\n    const { branchY, initialBarriers, slots, subtrees } = this.barrierDragState;\n\n    // 1. Lock dragged barrier strictly to branch Y (no vertical drift!)\n    draggedNode.y = branchY;\n    const draggedEl = document.getElementById(`node-${draggedNode.id}`);\n    if (draggedEl) {\n      draggedEl.style.top = `${Math.round(branchY)}px`;\n    }\n\n    // 2. Keep dragged barrier's subtree (EFs and mitigations) centered with it\n    const draggedSubtree = subtrees.get(draggedNode.id) || [];\n    draggedSubtree.forEach(item => {\n      const dNode = this.app.getNodeById(item.id);\n      if (dNode) {\n        dNode.x = Math.round(draggedNode.x + item.relX);\n        dNode.y = Math.round(branchY + item.relY);\n        const el = document.getElementById(`node-${dNode.id}`);\n        if (el) {\n          el.style.left = `${dNode.x}px`;\n          el.style.top = `${dNode.y}px`;\n        }\n      }\n    });\n\n    if (initialBarriers.length <= 1) {\n      return;\n    }\n\n    // 3. Calculate target slot based on draggedNode.x relative to slot midpoints\n    let targetSlot = 0;\n    while (\n      targetSlot < slots.length - 1 &&\n      draggedNode.x > (slots[targetSlot] + slots[targetSlot + 1]) / 2\n    ) {\n      targetSlot++;\n    }\n\n    // 4. Construct current order preview with draggedNode in targetSlot\n    const remaining = initialBarriers.filter(b => b.id !== draggedNode.id);\n    const currentOrder = [\n      ...remaining.slice(0, targetSlot),\n      draggedNode,\n      ...remaining.slice(targetSlot)\n    ];\n\n    // 5. Shift all OTHER barriers to their preview slot positions\n    currentOrder.forEach((barrier, slotIdx) => {\n      if (barrier.id === draggedNode.id) return;\n      const targetX = slots[slotIdx];\n      barrier.x = targetX;\n      barrier.y = branchY;\n\n      const bEl = document.getElementById(`node-${barrier.id}`);\n      if (bEl) {\n        bEl.style.left = `${Math.round(barrier.x)}px`;\n        bEl.style.top = `${Math.round(barrier.y)}px`;\n      }\n\n      // Shift this barrier's subtree with it\n      const bSubtree = subtrees.get(barrier.id) || [];\n      bSubtree.forEach(item => {\n        const dNode = this.app.getNodeById(item.id);\n        if (dNode) {\n          dNode.x = Math.round(barrier.x + item.relX);\n          dNode.y = Math.round(branchY + item.relY);\n          const el = document.getElementById(`node-${dNode.id}`);\n          if (el) {\n            el.style.left = `${dNode.x}px`;\n            el.style.top = `${dNode.y}px`;\n          }\n        }\n      });\n    });\n  }\n\n  // Snap all sibling barriers to exact canonical horizontal slots on drop and re-link tree\n  finalizeBarrierDragReorder(draggedNode) {\n    if (!this.barrierDragState) return;\n\n    const { branchType, branchY, branchRoot, threat, outcome, initialBarriers, slots, subtrees } = this.barrierDragState;\n\n    // 1. Lock dragged node to branch Y\n    draggedNode.y = branchY;\n\n    if (initialBarriers.length <= 1) {\n      // Only 1 barrier: snap Y and descendants\n      const draggedEl = document.getElementById(`node-${draggedNode.id}`);\n      if (draggedEl) {\n        draggedEl.style.top = `${Math.round(branchY)}px`;\n      }\n      const draggedSubtree = subtrees.get(draggedNode.id) || [];\n      draggedSubtree.forEach(item => {\n        const dNode = this.app.getNodeById(item.id);\n        if (dNode) {\n          dNode.x = Math.round(draggedNode.x + item.relX);\n          dNode.y = Math.round(branchY + item.relY);\n          const el = document.getElementById(`node-${dNode.id}`);\n          if (el) {\n            el.style.left = `${dNode.x}px`;\n            el.style.top = `${dNode.y}px`;\n          }\n        }\n      });\n      this.barrierDragState = null;\n      this.renderConnections();\n      this.app.scheduleAutoSave('Positioned barrier');\n      return;\n    }\n\n    // 2. Determine targetSlot\n    let targetSlot = 0;\n    while (\n      targetSlot < slots.length - 1 &&\n      draggedNode.x > (slots[targetSlot] + slots[targetSlot + 1]) / 2\n    ) {\n      targetSlot++;\n    }\n\n    const remaining = initialBarriers.filter(b => b.id !== draggedNode.id);\n    const finalOrder = [\n      ...remaining.slice(0, targetSlot),\n      draggedNode,\n      ...remaining.slice(targetSlot)\n    ];\n\n    // 3. Snap ALL barriers to their exact slot positions and branch Y\n    finalOrder.forEach((barrier, slotIdx) => {\n      barrier.x = slots[slotIdx];\n      barrier.y = branchY;\n\n      const bEl = document.getElementById(`node-${barrier.id}`);\n      if (bEl) {\n        bEl.style.left = `${Math.round(barrier.x)}px`;\n        bEl.style.top = `${Math.round(barrier.y)}px`;\n      }\n\n      // Snap subtree\n      const bSubtree = subtrees.get(barrier.id) || [];\n      bSubtree.forEach(item => {\n        const dNode = this.app.getNodeById(item.id);\n        if (dNode) {\n          dNode.x = Math.round(barrier.x + item.relX);\n          dNode.y = Math.round(branchY + item.relY);\n          const el = document.getElementById(`node-${dNode.id}`);\n          if (el) {\n            el.style.left = `${dNode.x}px`;\n            el.style.top = `${dNode.y}px`;\n          }\n        }\n      });\n    });\n\n    const orderChanged = finalOrder.some((b, i) => b.id !== initialBarriers[i].id);\n    if (!orderChanged) {\n      this.barrierDragState = null;\n      this.renderConnections();\n      return;\n    }\n\n    // 4. Re-link parent_id hierarchy based on branch type\n    if (branchType === 'threat' && threat) {\n      // Threat branch: Threat -> b_0 -> b_1 -> ... -> b_{K-1}\n      finalOrder[0].parent_id = threat.id;\n      for (let i = 1; i < finalOrder.length; i++) {\n        finalOrder[i].parent_id = finalOrder[i - 1].id;\n      }\n    } else if (branchType === 'outcome') {\n      // Outcome branch: outcome > barriers > escalation factor > mitigation\n      // Runs from right to left to attach to the top event:\n      // outcome -> finalOrder[k-1] -> ... -> finalOrder[0] -> topEvent\n      const topEvent = (branchRoot && branchRoot.node_type === 'top_event')\n        ? branchRoot\n        : (this.app.layout\n            ? this.app.layout.getTopEventForNode(draggedNode, this.app.workspace.nodes.filter(n => n.diagram_id === draggedNode.diagram_id))\n            : this.app.workspace.nodes.find(n => n.diagram_id === draggedNode.diagram_id && n.node_type === 'top_event'));\n      if (outcome) {\n        outcome.parent_id = topEvent ? topEvent.id : null;\n        outcome.y = branchY;\n        const k = finalOrder.length;\n        finalOrder[k - 1].parent_id = outcome.id;\n        for (let i = k - 2; i >= 0; i--) {\n          finalOrder[i].parent_id = finalOrder[i + 1].id;\n        }\n        const lastBarrier = finalOrder[k - 1];\n        const subW = this.app.layout ? this.app.layout.getItemSubtreeWidth(lastBarrier, this.app.workspace.nodes) : 135;\n        const minOutcomeX = lastBarrier.x + Math.max(lastBarrier.width || 135, subW) + 45;\n        if (outcome.x < minOutcomeX) {\n          outcome.x = minOutcomeX;\n        }\n        const oEl = document.getElementById(`node-${outcome.id}`);\n        if (oEl) {\n          oEl.style.left = `${Math.round(outcome.x)}px`;\n          oEl.style.top = `${Math.round(outcome.y)}px`;\n        }\n      }\n    }\n\n    // 5. Reorder in workspace.nodes to keep sequence clean\n    const barrierIds = new Set(finalOrder.map(b => b.id));\n    const firstIdx = this.app.workspace.nodes.findIndex(n => barrierIds.has(n.id));\n    if (firstIdx !== -1) {\n      const before = this.app.workspace.nodes.slice(0, firstIdx).filter(n => !barrierIds.has(n.id));\n      const after = this.app.workspace.nodes.slice(firstIdx).filter(n => !barrierIds.has(n.id));\n      this.app.workspace.nodes = [...before, ...finalOrder, ...after];\n    }\n\n    this.barrierDragState = null;\n    this.renderConnections();\n    if (this.app.tree) {\n      this.app.tree.render();\n    }\n    this.app.scheduleAutoSave('Reordered barriers along branch');\n    this.app.showNotification('Reordered barriers along branch', 'success');\n  }\n\n  // Calculate dynamic branch height taking into account all barriers and their escalation factors\n  getBranchHeightForIds(branchIds, allNodes) {\n    let maxBranchH = 80;\n    branchIds.forEach(bId => {\n      const node = allNodes.find(n => n.id === bId);\n      if (node && (node.node_type === 'threat_barrier' || node.node_type === 'outcome_barrier' || node.node_type === 'threat' || node.node_type === 'outcome')) {\n        const efs = allNodes.filter(n => n.parent_id === node.id && n.node_type === 'escalation_factor');\n        if (efs.length > 0) {\n          let totalEfsH = 0;\n          efs.forEach(ef => {\n            const mits = allNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n            const ySpread = mits.length > 1 ? (Math.max(...mits.map(n => n.y)) - Math.min(...mits.map(n => n.y))) : 0;\n            const xSpread = mits.length > 1 ? (Math.max(...mits.map(n => n.x)) - Math.min(...mits.map(n => n.x))) : 0;\n            const isVert = mits.length > 1 && (ySpread > 20 || ySpread >= xSpread);\n            totalEfsH += isVert ? Math.max(98, (mits.length * 95) + 15) : 98;\n          });\n          const efBranchH = (node.height || 80) + 25 + totalEfsH;\n          maxBranchH = Math.max(maxBranchH, efBranchH);\n        }\n      }\n    });\n    return maxBranchH;\n  }\n\n  // Calculate dynamic non-overlapping slot positions for an ordered array of branches\n  calculateSlotPositions(order, startY, branchGapY = 40) {\n    let currY = startY;\n    const posMap = new Map();\n    order.forEach(b => {\n      posMap.set(b, currY);\n      currY += b.height + branchGapY;\n    });\n    return posMap;\n  }\n\n  // =========================================================================\n  // Vertical Branch Drag Reordering (Threats & Outcomes with Live Shift Preview)\n  // =========================================================================\n  initBranchDrag(node) {\n    if (!node || (node.node_type !== 'threat' && node.node_type !== 'outcome' && node.node_type !== 'outcome_barrier')) {\n      this.branchDragState = null;\n      return;\n    }\n\n    const diagNodes = this.app.workspace.nodes.filter(n => n.diagram_id === node.diagram_id);\n    const topEvent = this.app.layout\n      ? this.app.layout.getTopEventForNode(node, diagNodes)\n      : diagNodes.find(n => n.node_type === 'top_event');\n    const teH = topEvent ? (topEvent.height || 130) : 130;\n    const cy = (topEvent && topEvent.y !== undefined && topEvent.y !== null) ? topEvent.y : 380;\n    const ownership = this.app.layout\n      ? this.app.layout.buildClusterOwnership(diagNodes, diagNodes.filter(n => n.node_type === 'top_event'))\n      : new Map();\n\n    const isThreat = (node.node_type === 'threat');\n    const branches = [];\n    const branchGapY = 40;\n\n    if (isThreat) {\n      const threats = (this.app.layout && topEvent)\n        ? this.app.layout.getClusterThreats(topEvent, diagNodes, ownership)\n        : diagNodes.filter(n => n.node_type === 'threat').sort((a, b) => (a.y || 0) - (b.y || 0));\n\n      if (threats.length <= 1) {\n        this.branchDragState = null;\n        return;\n      }\n\n      threats.forEach(t => {\n        const branchIds = this.getBranchIdsForNode(t);\n        const subtrees = new Map();\n        branchIds.forEach(bId => {\n          const bNode = this.app.getNodeById(bId);\n          if (bNode) {\n            let relY = 0;\n            if (bNode.node_type === 'escalation_factor') {\n              const siblingEFs = diagNodes\n                .filter(n => n.parent_id === bNode.parent_id && n.node_type === 'escalation_factor')\n                .sort((a, b) => (a.y || 0) - (b.y || 0));\n              const j = siblingEFs.findIndex(n => n.id === bNode.id);\n              relY = 80 + 25 + (Math.max(0, j) * 98);\n            } else if (bNode.node_type === 'mitigation') {\n              const parentEF = this.app.getNodeById(bNode.parent_id);\n              if (parentEF) {\n                const siblingEFs = diagNodes\n                  .filter(n => n.parent_id === parentEF.parent_id && n.node_type === 'escalation_factor')\n                  .sort((a, b) => (a.y || 0) - (b.y || 0));\n                const j = siblingEFs.findIndex(n => n.id === parentEF.id);\n                relY = 80 + 25 + (Math.max(0, j) * 98) + ((bNode.y || 0) - (parentEF.y || 0));\n              }\n            }\n            subtrees.set(bId, {\n              id: bId,\n              relX: bNode.x - t.x,\n              relY,\n            });\n          }\n        });\n\n        const height = this.getBranchHeightForIds(branchIds, diagNodes);\n\n        branches.push({\n          keyNode: t,\n          branchIds,\n          subtrees,\n          height,\n          baseY: t.y,\n          currentBaseY: t.y,\n        });\n      });\n    } else {\n      // Outcome branches: find right branches for this topEvent\n      const rightBranches = (this.app.layout && topEvent)\n        ? this.app.layout.getClusterRightBranches(topEvent, diagNodes, ownership)\n        : [];\n\n      rightBranches.forEach(branch => {\n        const chain = branch.chain;\n        const oc = branch.outcome || chain.find(n => n.node_type === 'outcome') || chain[chain.length - 1];\n        if (oc) {\n          const branchIds = this.getBranchIdsForNode(oc);\n          const subtrees = new Map();\n          const effectiveBaseY = (oc.y !== undefined && oc.y !== null) ? oc.y : branch.root.y;\n          branchIds.forEach(bId => {\n            const bNode = this.app.getNodeById(bId);\n            if (bNode) {\n              let relY = 0;\n              if (bNode.node_type === 'escalation_factor') {\n                const siblingEFs = diagNodes\n                  .filter(n => n.parent_id === bNode.parent_id && n.node_type === 'escalation_factor')\n                  .sort((a, b) => (a.y || 0) - (b.y || 0));\n                const j = siblingEFs.findIndex(n => n.id === bNode.id);\n                relY = 80 + 25 + (Math.max(0, j) * 98);\n              } else if (bNode.node_type === 'mitigation') {\n                const parentEF = this.app.getNodeById(bNode.parent_id);\n                if (parentEF) {\n                  const siblingEFs = diagNodes\n                    .filter(n => n.parent_id === parentEF.parent_id && n.node_type === 'escalation_factor')\n                    .sort((a, b) => (a.y || 0) - (b.y || 0));\n                  const j = siblingEFs.findIndex(n => n.id === parentEF.id);\n                  relY = 80 + 25 + (Math.max(0, j) * 98) + ((bNode.y || 0) - (parentEF.y || 0));\n                }\n              }\n              subtrees.set(bId, {\n                id: bId,\n                relX: bNode.x - oc.x,\n                relY,\n              });\n            }\n          });\n\n          const height = this.getBranchHeightForIds(branchIds, diagNodes);\n\n          branches.push({\n            keyNode: oc,\n            rootNode: branch.root || oc,\n            branchIds,\n            subtrees,\n            height,\n            baseY: effectiveBaseY,\n            currentBaseY: effectiveBaseY,\n          });\n        }\n      });\n\n      branches.sort((a, b) => a.baseY - b.baseY);\n\n      if (branches.length <= 1) {\n        this.branchDragState = null;\n        return;\n      }\n    }\n\n    const totalH = branches.reduce((acc, b) => acc + b.height, 0) + \n                   Math.max(0, branches.length - 1) * branchGapY;\n    const startY = cy + (teH / 2) - (totalH / 2);\n\n    this.branchDragState = {\n      isThreat,\n      draggedNodeId: node.id,\n      branches,\n      startY,\n      branchGapY,\n    };\n  }\n\n  // Smoothly shift sibling branches up or down as the dragged branch moves\n  handleBranchDragReorder(draggedNode) {\n    if (!this.branchDragState) return;\n\n    const { branches, startY, branchGapY } = this.branchDragState;\n    if (branches.length <= 1) return;\n\n    const draggedBranch = branches.find(b => b.branchIds.includes(draggedNode.id)) ||\n                          branches.find(b => b.keyNode.id === draggedNode.id);\n    if (!draggedBranch) return;\n    const otherBranches = branches.filter(b => b !== draggedBranch);\n\n    // Compute candidate target Y for each insertion slot k\n    const numSlots = branches.length;\n    const candidateY = [];\n    let accY = startY;\n    for (let k = 0; k < numSlots; k++) {\n      candidateY.push(accY);\n      if (k < otherBranches.length) {\n        accY += otherBranches[k].height + branchGapY;\n      }\n    }\n\n    // Determine targetSlot based on draggedNode.y relative to candidate thresholds\n    let targetSlot = 0;\n    while (\n      targetSlot < numSlots - 1 &&\n      draggedNode.y > (candidateY[targetSlot] + candidateY[targetSlot + 1]) / 2\n    ) {\n      targetSlot++;\n    }\n\n    const currentOrder = [\n      ...otherBranches.slice(0, targetSlot),\n      draggedBranch,\n      ...otherBranches.slice(targetSlot)\n    ];\n\n    // Compute exact non-overlapping Y position for every branch in currentOrder based on branch heights\n    const slotPositions = this.calculateSlotPositions(currentOrder, startY, branchGapY);\n\n    // Shift all other branches to their preview slot positions\n    currentOrder.forEach(branch => {\n      if (branch === draggedBranch) return;\n      const targetBaseY = slotPositions.get(branch);\n\n      if (branch.currentBaseY !== targetBaseY) {\n        branch.currentBaseY = targetBaseY;\n\n        branch.subtrees.forEach(item => {\n          const node = this.app.getNodeById(item.id);\n          if (node) {\n            node.y = Math.round(targetBaseY + item.relY);\n            const el = document.getElementById(`node-${node.id}`);\n            if (el) {\n              el.style.top = `${node.y}px`;\n            }\n          }\n        });\n      }\n    });\n  }\n\n  // Finalize branch reordering and settle into target slot\n  finalizeBranchDragReorder(draggedNode) {\n    if (!this.branchDragState) return;\n\n    const { branches, startY, branchGapY } = this.branchDragState;\n\n    const draggedBranch = branches.find(b => b.branchIds.includes(draggedNode.id)) ||\n                          branches.find(b => b.keyNode.id === draggedNode.id);\n    if (!draggedBranch) {\n      this.branchDragState = null;\n      return;\n    }\n    const otherBranches = branches.filter(b => b !== draggedBranch);\n\n    // Compute candidate target Y for each insertion slot k\n    const numSlots = branches.length;\n    const candidateY = [];\n    let accY = startY;\n    for (let k = 0; k < numSlots; k++) {\n      candidateY.push(accY);\n      if (k < otherBranches.length) {\n        accY += otherBranches[k].height + branchGapY;\n      }\n    }\n\n    let targetSlot = 0;\n    while (\n      targetSlot < numSlots - 1 &&\n      draggedNode.y > (candidateY[targetSlot] + candidateY[targetSlot + 1]) / 2\n    ) {\n      targetSlot++;\n    }\n\n    const finalOrder = [\n      ...otherBranches.slice(0, targetSlot),\n      draggedBranch,\n      ...otherBranches.slice(targetSlot)\n    ];\n\n    // Compute exact non-overlapping Y position for every branch in finalOrder based on branch heights\n    const slotPositions = this.calculateSlotPositions(finalOrder, startY, branchGapY);\n\n    // Settle each branch smoothly into its slot\n    finalOrder.forEach(branch => {\n      const targetBaseY = slotPositions.get(branch);\n      branch.subtrees.forEach(item => {\n        const node = this.app.getNodeById(item.id);\n        if (node) {\n          node.y = Math.round(targetBaseY + item.relY);\n          const el = document.getElementById(`node-${node.id}`);\n          if (el) {\n            el.style.top = `${node.y}px`;\n          }\n        }\n      });\n    });\n\n    const orderChanged = finalOrder.some((b, i) => b !== branches[i]);\n    if (!orderChanged) {\n      this.branchDragState = null;\n      this.renderConnections();\n      return;\n    }\n\n    // Reorder ALL nodes across these branches in workspace.nodes to maintain consistent array order\n    const allWingNodeIds = new Set();\n    finalOrder.forEach(b => b.branchIds.forEach(id => allWingNodeIds.add(id)));\n    const firstIdx = this.app.workspace.nodes.findIndex(n => allWingNodeIds.has(n.id));\n    if (firstIdx !== -1) {\n      const reorderedWingNodes = [];\n      finalOrder.forEach(b => {\n        b.branchIds.forEach(id => {\n          const node = this.app.getNodeById(id);\n          if (node && !reorderedWingNodes.some(rn => rn.id === node.id)) {\n            reorderedWingNodes.push(node);\n          }\n        });\n      });\n      const before = this.app.workspace.nodes.slice(0, firstIdx).filter(n => !allWingNodeIds.has(n.id));\n      const after = this.app.workspace.nodes.slice(firstIdx).filter(n => !allWingNodeIds.has(n.id));\n      this.app.workspace.nodes = [...before, ...reorderedWingNodes, ...after];\n    }\n\n    this.branchDragState = null;\n    this.renderConnections();\n    if (this.app.tree) {\n      this.app.tree.render();\n    }\n    this.app.scheduleAutoSave('Reordered branches vertically');\n    this.app.showNotification('Reordered branches vertically', 'success');\n  }\n\n  // Get all descendant IDs recursively for hierarchical dragging\n  getDescendantIds(rootNodeId) {\n    const descendants = new Set();\n    const queue = [rootNodeId];\n\n    while (queue.length > 0) {\n      const currentId = queue.shift();\n      const children = this.app.workspace.nodes.filter(n => n.parent_id === currentId);\n      for (const child of children) {\n        if (!descendants.has(child.id)) {\n          descendants.add(child.id);\n          queue.push(child.id);\n        }\n      }\n    }\n    return Array.from(descendants);\n  }\n\n  // Get all node IDs associated with this element's branch for dragging.\n  // For threats: all downstream barriers, escalation factors, mitigations.\n  // For outcomes: all upstream barriers along the outcome chain leading from Top Event,\n  // plus all attached escalation factors and mitigations, staging the entire branch in unison.\n  getBranchIdsForNode(node) {\n    if (!node) return [];\n\n    // When dragging an outcome or outcome barrier, stage the entire outcome branch\n    if (node.node_type === 'outcome' || node.node_type === 'outcome_barrier') {\n      const diagNodes = this.app.workspace.nodes.filter(n => n.diagram_id === node.diagram_id);\n      const topEvent = this.app.layout\n        ? this.app.layout.getTopEventForNode(node, diagNodes)\n        : diagNodes.find(n => n.node_type === 'top_event');\n      const rightBranches = (this.app.layout && topEvent)\n        ? this.app.layout.getClusterRightBranches(topEvent, diagNodes)\n        : [];\n\n      let matchedChain = null;\n      for (const branch of rightBranches) {\n        if (branch.chain.some(n => n.id === node.id)) {\n          matchedChain = branch.chain;\n          break;\n        }\n      }\n\n      if (matchedChain && matchedChain.length > 0) {\n        const branchIds = new Set();\n        matchedChain.forEach(item => {\n          branchIds.add(item.id);\n          const descs = this.getDescendantIds(item.id);\n          descs.forEach(dId => branchIds.add(dId));\n        });\n        return Array.from(branchIds);\n      }\n\n      // Fallback: trace backward via parent_id to topEvent\n      const branchIds = new Set([node.id]);\n      let curr = node;\n      while (curr && curr.parent_id && curr.parent_id !== (topEvent ? topEvent.id : null)) {\n        const parent = this.app.getNodeById(curr.parent_id);\n        if (!parent || parent.node_type === 'top_event' || parent.node_type === 'hazard') break;\n        branchIds.add(parent.id);\n        const descs = this.getDescendantIds(parent.id);\n        descs.forEach(dId => branchIds.add(dId));\n        curr = parent;\n      }\n      const selfDescs = this.getDescendantIds(node.id);\n      selfDescs.forEach(dId => branchIds.add(dId));\n      return Array.from(branchIds);\n    }\n\n    if (node.node_type === 'threat_barrier') {\n      const diagNodes = this.app.workspace.nodes.filter(n => n.diagram_id === node.diagram_id);\n      const attached = this.getBarrierSubtreeItems(node, diagNodes);\n      return [node.id, ...attached.map(it => it.id)];\n    }\n\n    // Default for threats, escalation factors, mitigations: hierarchical descendants\n    const ids = new Set([node.id]);\n    const descendants = this.getDescendantIds(node.id);\n    descendants.forEach(dId => ids.add(dId));\n    return Array.from(ids);\n  }\n\n  // Render all nodes and connectors on the canvas\n  render() {\n    this.renderNodes();\n    this.renderConnections();\n  }\n\n  renderNodes() {\n    this.nodesLayer.innerHTML = '';\n    if (!this.app.workspace || !this.app.workspace.nodes) return;\n    const nodes = this.app.currentDiagram\n      ? this.app.workspace.nodes.filter(n => n.diagram_id === this.app.currentDiagram.id)\n      : [];\n    if (!nodes || nodes.length === 0) return;\n\n    const customNames = this.app.getCustomNames();\n    const customColors = this.app.getCustomColors();\n\n    nodes.forEach(node => {\n      const el = document.createElement('div');\n      el.id = `node-${node.id}`;\n      el.className = `bowtie-node node-${node.node_type.replace('_', '-')}`;\n      if (node.id === this.activeNodeId) {\n        el.classList.add('active-node');\n      }\n\n      const standardHeight = (node.node_type === 'top_event') ? 130 : ((node.node_type === 'hazard') ? 90 : 80);\n      node.height = standardHeight;\n      el.style.left = `${node.x}px`;\n      el.style.top = `${node.y}px`;\n      el.style.width = `${node.width || 135}px`;\n      el.style.height = `${standardHeight}px`;\n      el.style.minHeight = `${standardHeight}px`;\n\n      // Apply custom colors if defined\n      const typeKey = node.node_type;\n      const themeColor = customColors[typeKey];\n      const customTitle = this.app ? this.app.getNodeDisplayTitle(node) : (customNames[typeKey] || node.title);\n\n      // Inner HTML structure matching screenshot\n      let extraPinsHtml = '';\n      if (node.node_type === 'top_event') {\n        extraPinsHtml = `<div class=\"node-pin-left\"></div><div class=\"node-pin-right\"></div>`;\n      }\n\n      // Check if node is linked to a global master element\n      const isGlobal = !!node.global_element_id;\n      const globalIndicator = isGlobal ? `<span title=\"Linked to Global Master Element\" style=\"font-size: 10px; margin-right: 4px; opacity: 0.85;\">🌐</span>` : '';\n\n      // Check for attached documents\n      const attCount = node.attachment_count || (this.app.workspace && this.app.workspace.attachments ? this.app.workspace.attachments.filter(a => a.node_id === node.id).length : 0);\n      const attBadge = attCount > 0 ? `<span class=\"node-card-att-badge\" title=\"${attCount} attached document(s)\">📎 ${attCount}</span>` : '';\n\n      const suggestedText = this.getNodeSuggestedDescription(node);\n\n      el.innerHTML = `\n        ${extraPinsHtml}\n        <div class=\"node-header\" ${themeColor && node.node_type !== 'hazard' && node.node_type !== 'top_event' ? `style=\"background-color: ${themeColor};\"` : ''}>\n          <div class=\"node-title-text\">${globalIndicator}${this.escapeHtml(customTitle)}${attBadge}</div>\n          <div class=\"node-actions\">\n            ${this.renderNodeActionButtons(node)}\n          </div>\n        </div>\n        <div class=\"node-body\">\n          <textarea class=\"node-textarea\" placeholder=\"${this.escapeHtml(suggestedText)}\">${this.escapeHtml(node.description || '')}</textarea>\n        </div>\n      `;\n\n      // Apply border and background color customizations\n      if (themeColor) {\n        if (node.node_type === 'hazard' || node.node_type === 'top_event') {\n          el.style.borderColor = themeColor;\n        } else {\n          el.style.borderColor = themeColor;\n          el.style.backgroundColor = themeColor;\n        }\n      }\n\n      // Click to select\n      el.addEventListener('mousedown', (e) => {\n        // Ignore if clicking textarea or action buttons for drag start\n        if (e.target.tagName === 'TEXTAREA' || e.target.closest('.node-btn')) {\n          this.setActiveNode(node.id);\n          return;\n        }\n\n        e.stopPropagation();\n        this.setActiveNode(node.id);\n\n        if (!this.app.canEditCurrentDiagram()) {\n          this.isDraggingNode = false;\n          this.draggedNodeId = null;\n          return;\n        }\n\n        // Start Hierarchical Dragging!\n        this.isDraggingNode = true;\n        this.nodeDragMoved = false;\n        this.draggedNodeId = node.id;\n        this.dragStartMouseX = e.clientX;\n        this.dragStartMouseY = e.clientY;\n        this.dragInitialNodesSnapshot = this.app.captureWorkspaceSnapshot();\n\n        // Capture initial positions of this node + all its branch/descendant elements\n        this.dragInitialPositions.clear();\n        const branchIds = this.getBranchIdsForNode(node);\n        branchIds.forEach(bId => {\n          const bNode = this.app.getNodeById(bId);\n          if (bNode) {\n            this.dragInitialPositions.set(bId, { x: bNode.x, y: bNode.y });\n          }\n        });\n\n        if (node.node_type === 'mitigation') {\n          this.initMitigationDrag(node);\n        } else if (node.node_type === 'escalation_factor') {\n          this.initEscalationFactorDrag(node);\n        } else if (node.node_type === 'threat_barrier') {\n          this.initBarrierDrag(node);\n        } else if (node.node_type === 'outcome_barrier') {\n          this.initBranchDrag(node);\n          this.initBarrierDrag(node);\n          this.barrierDragMode = null;\n        } else if (node.node_type === 'threat' || node.node_type === 'outcome') {\n          this.initBranchDrag(node);\n        }\n      });\n\n      // Handle Textarea input\n      const textarea = el.querySelector('.node-textarea');\n      if (textarea) {\n        if (!this.app.canEditCurrentDiagram()) {\n          textarea.readOnly = true;\n          textarea.style.cursor = 'default';\n        }\n        textarea.addEventListener('input', (e) => {\n          if (!this.app.canEditCurrentDiagram()) return;\n          node.description = e.target.value;\n          this.app.onNodeTextareaChanged(node);\n        });\n        textarea.addEventListener('blur', () => {\n          if (window.innerWidth <= 900 || ('ontouchstart' in window)) {\n            window.scrollTo(0, 0);\n          }\n        });\n      }\n\n      // Wire action buttons\n      this.wireNodeActionButtons(el, node);\n\n      this.nodesLayer.appendChild(el);\n    });\n  }\n\n  getNodeSuggestedDescription(node) {\n    if (!node) return 'e.g. Enter description...';\n    if (node._suggestedPlaceholder) {\n      return node._suggestedPlaceholder;\n    }\n    const pool = (this.app && typeof this.app.getElementSuggestions === 'function')\n      ? this.app.getElementSuggestions(node.node_type)\n      : null;\n    if (pool && pool.length > 0) {\n      let index = 0;\n      if (node.id) {\n        let hash = 0;\n        for (let i = 0; i < node.id.length; i++) {\n          hash = ((hash << 5) - hash) + node.id.charCodeAt(i);\n          hash |= 0;\n        }\n        index = Math.abs(hash) % pool.length;\n      } else {\n        index = Math.floor(Math.random() * pool.length);\n      }\n      const suggestion = pool[index];\n      const prefix = (suggestion && suggestion.toLowerCase().startsWith('e.g.')) ? '' : 'e.g. ';\n      node._suggestedPlaceholder = `${prefix}${suggestion}`;\n      return node._suggestedPlaceholder;\n    }\n    return 'e.g. Enter description...';\n  }\n\n  getNodeDisplayTitle(node) {\n    return this.app ? this.app.getNodeDisplayTitle(node) : (node ? (node.title || '') : '');\n  }\n\n  renderNodeActionButtons(node) {\n    if (!this.app.canEditCurrentDiagram()) {\n      return '';\n    }\n    // Only '+' button to add logical child to node (threat -> barrier, barrier -> escalation factor, etc.)\n    // Delete is performed via the element inspector panel; runner is triggered via the top bar.\n    let addBtn = '';\n\n    if (node.node_type === 'threat') {\n      addBtn = `<button class=\"node-btn btn-node-add\" title=\"Add Barrier to Threat\">+</button>`;\n    } else if (node.node_type === 'threat_barrier' || node.node_type === 'outcome_barrier') {\n      addBtn = `<button class=\"node-btn btn-node-add\" title=\"Add Escalation Factor\">+</button>`;\n    } else if (node.node_type === 'escalation_factor') {\n      addBtn = `<button class=\"node-btn btn-node-add\" title=\"Add Mitigation Barrier\">+</button>`;\n    } else if (node.node_type === 'outcome') {\n      addBtn = `<button class=\"node-btn btn-node-add\" title=\"Add Barrier to Outcome\">+</button>`;\n    }\n\n    return addBtn;\n  }\n\n  wireNodeActionButtons(el, node) {\n    const addBtn = el.querySelector('.btn-node-add');\n    if (addBtn) {\n      addBtn.addEventListener('click', (e) => {\n        e.stopPropagation();\n        this.app.handleAddChildNode(node);\n      });\n    }\n\n    const delBtn = el.querySelector('.btn-node-del');\n    if (delBtn) {\n      delBtn.addEventListener('click', (e) => {\n        e.stopPropagation();\n        this.app.handleDeleteNode(node.id);\n      });\n    }\n  }\n\n  // Draw clean canonical Bowtie trunk-and-branch wire connections matching authentic Bowtie structure\n  renderConnections() {\n    try {\n      this.connectionsGroup.innerHTML = '';\n      const workspace = this.app.workspace;\n      if (!workspace || !workspace.diagrams || !workspace.nodes) return;\n\n    const addPath = (d, classes = ['connection-wire'], fromId = null, toId = null) => {\n      if (!d) return null;\n      const pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n      pathEl.setAttribute('d', d);\n      pathEl.setAttribute('class', classes.join(' '));\n      if (fromId) pathEl.setAttribute('data-from-id', fromId);\n      if (toId) pathEl.setAttribute('data-to-id', toId);\n      this.connectionsGroup.appendChild(pathEl);\n      return pathEl;\n    };\n\n    const connectOrthogonal = (sX, sY, tX, tY) => {\n      if (Math.abs(sY - tY) < 6) {\n        const avgY = Math.round((sY + tY) / 2);\n        return `M ${sX} ${avgY} L ${tX} ${avgY}`;\n      }\n      const midX = Math.round((sX + tX) / 2);\n      return `M ${sX} ${sY} L ${midX} ${sY} L ${midX} ${tY} L ${tX} ${tY}`;\n    };\n\n    const targetDiagrams = this.app.currentDiagram\n      ? [this.app.currentDiagram]\n      : [];\n\n    targetDiagrams.forEach(diag => {\n      const diagNodes = workspace.nodes.filter(n => n.diagram_id === diag.id);\n      if (diagNodes.length === 0) return;\n\n      const topEvents = diagNodes.filter(n => n.node_type === 'top_event');\n      const topEvent = topEvents[0] || null;\n      const hazards = diagNodes.filter(n => n.node_type === 'hazard');\n      const hazard = hazards[0] || null;\n\n      // Track nodes whose connections have already been handled to prevent duplicate wires\n      const handledNodeIds = new Set();\n      diagNodes.forEach(n => {\n        if (n.node_type === 'hazard' || n.node_type === 'top_event') {\n          handledNodeIds.add(n.id);\n        }\n      });\n\n      // 1. Hazard -> Top Event (Clean straight vertical wire for each Top Event and its Hazard)\n      topEvents.forEach(te => {\n        let hNode = null;\n        if (te.parent_id) {\n          hNode = diagNodes.find(n => n.id === te.parent_id);\n        }\n        if (!hNode) {\n          // Find hazard positioned closest directly above this top event if parent_id is missing\n          const hazardsAbove = diagNodes\n            .filter(n => n.node_type === 'hazard' && (n.y === undefined || n.y < te.y))\n            .sort((a, b) => Math.abs((a.x || 0) - (te.x || 0)) - Math.abs((b.x || 0) - (te.x || 0)) || (b.y || 0) - (a.y || 0));\n          hNode = hazardsAbove[0] || diagNodes.find(n => n.node_type === 'hazard');\n        }\n        if (hNode) {\n          const hW = hNode.width || 150;\n          const hH = hNode.height || 90;\n          const teW = te.width || 130;\n          const sX = hNode.x + hW / 2;\n          const sY = hNode.y + hH;\n          const tX = te.x + teW / 2;\n          const tY = te.y;\n          const isAct = (hNode.id === this.activeNodeId || te.id === this.activeNodeId);\n          addPath(`M ${sX} ${sY} L ${tX} ${tY}`, isAct ? ['connection-wire', 'active-branch'] : ['connection-wire'], hNode.id, te.id);\n          handledNodeIds.add(te.id);\n          handledNodeIds.add(hNode.id);\n        }\n      });\n\n      if (topEvents.length === 0) return;\n\n      // Helper to render escalation factors & mitigations under a barrier\n      const renderBarrierEscalationTree = (barrier, te = null) => {\n        handledNodeIds.add(barrier.id);\n        const efs = diagNodes\n          .filter(n => n.parent_id === barrier.id && n.node_type === 'escalation_factor')\n          .sort((a, b) => (a.y || 0) - (b.y || 0));\n        if (efs.length === 0) return;\n\n        const bCenterX = Math.round(barrier.x + (barrier.width || 135) / 2);\n        const bBottom = Math.round(barrier.y + 80);\n\n        efs.forEach((ef, j) => {\n          handledNodeIds.add(ef.id);\n          const efCenterX = Math.round(ef.x + (ef.width || 130) / 2);\n          const efTop = Math.round(ef.y);\n          const isEfAct = (barrier.id === this.activeNodeId || ef.id === this.activeNodeId);\n          const efCls = isEfAct ? ['connection-wire', 'active-branch'] : ['connection-wire'];\n\n          if (j === 0) {\n            // Drop from barrier bottom to first EF\n            if (Math.abs(efCenterX - bCenterX) < 5) {\n              addPath(`M ${bCenterX} ${bBottom} L ${bCenterX} ${efTop}`, efCls, barrier.id, ef.id);\n            } else {\n              const midY = (bBottom + efTop) / 2;\n              addPath(`M ${bCenterX} ${bBottom} L ${bCenterX} ${midY} L ${efCenterX} ${midY} L ${efCenterX} ${efTop}`, efCls, barrier.id, ef.id);\n            }\n          } else {\n            // Vertical step between consecutive EFs\n            const prevEf = efs[j - 1];\n            const prevBottom = Math.round(prevEf.y + 80);\n            const prevCenterX = Math.round(prevEf.x + (prevEf.width || 130) / 2);\n            if (Math.abs(efCenterX - prevCenterX) < 5) {\n              addPath(`M ${prevCenterX} ${prevBottom} L ${prevCenterX} ${efTop}`, efCls, prevEf.id, ef.id);\n            } else {\n              const midY = (prevBottom + efTop) / 2;\n              addPath(`M ${prevCenterX} ${prevBottom} L ${prevCenterX} ${midY} L ${efCenterX} ${midY} L ${efCenterX} ${efTop}`, efCls, prevEf.id, ef.id);\n            }\n          }\n\n          // Mitigations attached to EF (supports both vertical stack & horizontal row)\n          const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n          if (mits.length > 0) {\n            const isThreatSide = barrier.node_type === 'threat_barrier' || (te ? barrier.x < te.x : (topEvents[0] && barrier.x < topEvents[0].x));\n            const ySpread = Math.max(...mits.map(n => n.y)) - Math.min(...mits.map(n => n.y));\n            const xSpread = Math.max(...mits.map(n => n.x)) - Math.min(...mits.map(n => n.x));\n            const isVertical = ySpread > 20 || (ySpread >= xSpread && mits.length > 1);\n\n            if (isVertical) {\n              mits.sort((a, b) => a.y - b.y); // Vertical: top to bottom\n            } else if (isThreatSide) {\n              mits.sort((a, b) => b.x - a.x); // Threat side horizontal: closest to EF (highest x) first\n            } else {\n              mits.sort((a, b) => a.x - b.x); // Outcome side horizontal: closest to EF (lowest x) first\n            }\n\n            mits.forEach((mit, m) => {\n              handledNodeIds.add(mit.id);\n              const isMitAct = isEfAct || (mit.id === this.activeNodeId);\n              const mitCls = isMitAct ? ['connection-wire', 'active-branch'] : ['connection-wire'];\n\n              const efPinY = Math.round(ef.y + 40);\n              const mitPinY = Math.round(mit.y + (mit.height || 75) / 2);\n\n              if (isVertical) {\n                // Vertical stack: each mitigation branches directly and cleanly from the EF pin\n                if (isThreatSide) {\n                  const sX = ef.x;\n                  const tX = mit.x + (mit.width || 120);\n                  addPath(connectOrthogonal(sX, efPinY, tX, mitPinY), mitCls, ef.id, mit.id);\n                } else {\n                  const sX = ef.x + (ef.width || 130);\n                  const tX = mit.x;\n                  addPath(connectOrthogonal(sX, efPinY, tX, mitPinY), mitCls, ef.id, mit.id);\n                }\n              } else if (isThreatSide) {\n                // Threat side: Mitigations sit to the LEFT of the Escalation Factor\n                if (m === 0) {\n                  const sX = ef.x;\n                  const tX = mit.x + (mit.width || 120);\n                  addPath(connectOrthogonal(sX, efPinY, tX, mitPinY), mitCls, ef.id, mit.id);\n                } else {\n                  const prevMit = mits[m - 1];\n                  const sX = prevMit.x;\n                  const prevPinY = Math.round(prevMit.y + 40);\n                  const tX = mit.x + (mit.width || 120);\n                  addPath(connectOrthogonal(sX, prevPinY, tX, mitPinY), mitCls, prevMit.id, mit.id);\n                }\n              } else {\n                // Outcome side: Mitigations sit to the RIGHT of the Escalation Factor\n                if (m === 0) {\n                  const sX = ef.x + (ef.width || 130);\n                  const tX = mit.x;\n                  addPath(connectOrthogonal(sX, efPinY, tX, mitPinY), mitCls, ef.id, mit.id);\n                } else {\n                  const prevMit = mits[m - 1];\n                  const sX = prevMit.x + (prevMit.width || 120);\n                  const prevPinY = Math.round(prevMit.y + 40);\n                  const tX = mit.x;\n                  addPath(connectOrthogonal(sX, prevPinY, tX, mitPinY), mitCls, prevMit.id, mit.id);\n                }\n              }\n            });\n          }\n        });\n      };\n\n      // Ensure any outcome whose parent node was deleted or undone connects to topEvent\n      diagNodes.forEach(n => {\n        if (n.node_type === 'outcome') {\n          const parent = n.parent_id ? diagNodes.find(p => p.id === n.parent_id) : null;\n          if (!parent) {\n            const closestTe = topEvents.slice().sort((a, b) => Math.abs((n.y || 0) - (a.y || 0)) - Math.abs((n.y || 0) - (b.y || 0)))[0];\n            n.parent_id = closestTe ? closestTe.id : (topEvents[0] ? topEvents[0].id : null);\n          }\n        }\n      });\n\n      const layout = this.app.layout;\n      const ownership = layout ? layout.buildClusterOwnership(diagNodes, topEvents) : new Map();\n\n      topEvents.forEach(topEvent => {\n        const teW = topEvent.width || 130;\n        const teH = topEvent.height || 130;\n        const teCenterY = topEvent.y + teH / 2;\n        const leftTrunkX = topEvent.x - 55;\n        const rightTrunkX = topEvent.x + teW + 55;\n\n        // ----------------------------------------------------------------------\n        // 2. LEFT WING: Threats & Pre-Event Barriers for this Top Event\n        // ----------------------------------------------------------------------\n        const threats = layout\n          ? layout.getClusterThreats(topEvent, diagNodes, ownership)\n          : diagNodes.filter(n => n.node_type === 'threat' && n.parent_id === topEvent.id);\n\n        const leftBranches = [];\n        threats.forEach(threat => {\n          handledNodeIds.add(threat.id);\n          const barriers = this.app.layout ? this.app.layout.getLinearChainForThreat(threat, diagNodes) : [];\n          barriers.forEach(b => handledNodeIds.add(b.id));\n          leftBranches.push({ threat, barriers });\n        });\n\n        if (leftBranches.length > 0) {\n          // Collect branch connection Ys at trunk\n          const branchYs = leftBranches.map(b => {\n            const nearest = (b.barriers.length > 0) ? b.barriers[b.barriers.length - 1] : b.threat;\n            return Math.round(nearest.y + 40);\n          });\n\n          const minLeftY = Math.min(teCenterY, ...branchYs);\n          const maxLeftY = Math.max(teCenterY, ...branchYs);\n\n          const leftActive = leftBranches.some(b => {\n            if (b.threat.id === this.activeNodeId) return true;\n            return b.barriers.some(br => br.id === this.activeNodeId);\n          }) || (topEvent.id === this.activeNodeId);\n\n          const leftTrunkCls = leftActive ? ['connection-wire', 'active-branch'] : ['connection-wire'];\n\n          // Feeder line from Top Event left pin to Left Trunk\n          addPath(`M ${topEvent.x} ${teCenterY} L ${leftTrunkX} ${teCenterY}`, leftTrunkCls);\n\n          // Vertical Left Trunk line\n          addPath(`M ${leftTrunkX} ${minLeftY} L ${leftTrunkX} ${maxLeftY}`, leftTrunkCls);\n\n          // Draw horizontal pathway lines for each threat branch\n          leftBranches.forEach(branch => {\n            const t = branch.threat;\n            const bList = branch.barriers; // [b_0, ..., b_{k-1}]\n            const isBranchAct = (t.id === this.activeNodeId || bList.some(br => br.id === this.activeNodeId) || topEvent.id === this.activeNodeId);\n            const bCls = isBranchAct ? ['connection-wire', 'active-branch'] : ['connection-wire'];\n\n            if (bList.length === 0) {\n              // Forward path from Threat through Left Trunk into Top Event\n              const tY = Math.round(t.y + 40);\n              const tRight = Math.round(t.x + (t.width || 140));\n              const tToTePath = `M ${tRight} ${tY} L ${leftTrunkX} ${tY} L ${leftTrunkX} ${teCenterY} L ${topEvent.x} ${teCenterY}`;\n              addPath(tToTePath, bCls, t.id, topEvent.id);\n            } else {\n              // Forward path from rightmost barrier through Left Trunk into Top Event\n              const lastB = bList[bList.length - 1];\n              const lastBY = Math.round(lastB.y + 40);\n              const lastBRight = Math.round(lastB.x + (lastB.width || 135));\n              const lastBToTePath = `M ${lastBRight} ${lastBY} L ${leftTrunkX} ${lastBY} L ${leftTrunkX} ${teCenterY} L ${topEvent.x} ${teCenterY}`;\n              addPath(lastBToTePath, bCls, lastB.id, topEvent.id);\n\n              // Lines between consecutive barriers: bList[i] -> bList[i-1]\n              for (let i = bList.length - 1; i > 0; i--) {\n                const rightB = bList[i];\n                const leftB = bList[i - 1];\n                const rY = Math.round(rightB.y + 40);\n                const lY = Math.round(leftB.y + 40);\n                const lRight = Math.round(leftB.x + (leftB.width || 135));\n                addPath(connectOrthogonal(lRight, lY, Math.round(rightB.x), rY), bCls, leftB.id, rightB.id);\n              }\n\n              // Line from leftmost barrier (bList[0]) to Threat\n              const firstB = bList[0];\n              const fbY = Math.round(firstB.y + 40);\n              const tY = Math.round(t.y + 40);\n              const tRight = Math.round(t.x + (t.width || 140));\n              addPath(connectOrthogonal(tRight, tY, Math.round(firstB.x), fbY), bCls, t.id, firstB.id);\n            }\n\n            // Render escalation trees for barriers\n            bList.forEach(barrier => {\n              renderBarrierEscalationTree(barrier, topEvent);\n            });\n          });\n        }\n\n        // ----------------------------------------------------------------------\n        // 3. RIGHT WING: Post-Event Outcome Barriers & Outcomes for this Top Event\n        // ----------------------------------------------------------------------\n        const rightBranches = layout\n          ? layout.getClusterRightBranches(topEvent, diagNodes, ownership)\n          : [];\n\n        if (rightBranches.length > 0) {\n          // Collect branch connection Ys at trunk\n          const branchYs = rightBranches.map(b => {\n            const first = b.chain[0];\n            return Math.round(first.y + 40);\n          });\n\n          const minRightY = Math.min(teCenterY, ...branchYs);\n          const maxRightY = Math.max(teCenterY, ...branchYs);\n\n          const rightActive = rightBranches.some(b => {\n            return b.chain.some(item => item.id === this.activeNodeId);\n          }) || (topEvent.id === this.activeNodeId);\n\n          const rightTrunkCls = rightActive ? ['connection-wire', 'active-branch'] : ['connection-wire'];\n\n          // Feeder line from Top Event right pin to Right Trunk\n          addPath(`M ${topEvent.x + teW} ${teCenterY} L ${rightTrunkX} ${teCenterY}`, rightTrunkCls);\n\n          // Vertical Right Trunk line\n          addPath(`M ${rightTrunkX} ${minRightY} L ${rightTrunkX} ${maxRightY}`, rightTrunkCls);\n\n          // Draw horizontal pathway lines for each outcome branch\n          rightBranches.forEach(branch => {\n            const chain = branch.chain; // [b_0, b_1, ..., outcome]\n            if (chain.length === 0) return;\n\n            const isBranchAct = (chain.some(item => item.id === this.activeNodeId) || topEvent.id === this.activeNodeId);\n            const bCls = isBranchAct ? ['connection-wire', 'active-branch'] : ['connection-wire'];\n\n            // Contiguous forward path from Top Event through Right Trunk to chain[0]\n            const first = chain[0];\n            handledNodeIds.add(first.id);\n            const firstY = Math.round(first.y + 40);\n            const teToBranchPath = `M ${topEvent.x + teW} ${teCenterY} L ${rightTrunkX} ${teCenterY} L ${rightTrunkX} ${firstY} L ${Math.round(first.x)} ${firstY}`;\n            addPath(teToBranchPath, bCls, topEvent.id, first.id);\n\n            // Lines between consecutive items in chain\n            for (let i = 0; i < chain.length - 1; i++) {\n              const fromItem = chain[i];\n              const toItem = chain[i + 1];\n              handledNodeIds.add(toItem.id);\n              const fromRight = Math.round(fromItem.x + (fromItem.width || 135));\n              const fromY = Math.round(fromItem.y + 40);\n              const toY = Math.round(toItem.y + 40);\n              addPath(connectOrthogonal(fromRight, fromY, Math.round(toItem.x), toY), bCls, fromItem.id, toItem.id);\n            }\n\n            // Render escalation trees for barriers in chain\n            chain.forEach(item => {\n              renderBarrierEscalationTree(item, topEvent);\n            });\n          });\n        }\n      });\n\n      // 4. Fallback for any nodes not yet handled by primary branches (guarantees NO missing wires on dragging or re-parenting)\n      diagNodes.forEach(node => {\n        if (!node.parent_id || handledNodeIds.has(node.id)) return;\n        if (node.node_type === 'hazard' || node.node_type === 'top_event') return;\n        const parent = diagNodes.find(n => n.id === node.parent_id);\n        if (parent) {\n          if (parent.node_type === 'hazard') return;\n          const isLeft = (node.x < parent.x);\n          const sX = Math.round(isLeft ? parent.x : (parent.x + (parent.width || 135)));\n          const sY = Math.round(parent.y + 40);\n          const tX = Math.round(isLeft ? (node.x + (node.width || 135)) : node.x);\n          const tY = Math.round(node.y + 40);\n          const isAct = (node.id === this.activeNodeId || parent.id === this.activeNodeId);\n          addPath(connectOrthogonal(sX, sY, tX, tY), isAct ? ['connection-wire', 'active-branch'] : ['connection-wire'], parent.id, node.id);\n          handledNodeIds.add(node.id);\n        }\n      });\n    });\n    } catch (err) {\n      console.error('Error rendering connections:', err);\n    }\n  }\n\n  setActiveNode(nodeId) {\n    this.activeNodeId = nodeId;\n    document.querySelectorAll('.bowtie-node').forEach(el => {\n      el.classList.toggle('active-node', el.id === `node-${nodeId}`);\n    });\n    this.renderConnections();\n    this.app.onActiveNodeSelected(nodeId);\n\n    if (this.autoZoomEnabled) {\n      if (nodeId) {\n        this.zoomOnActiveElement(true);\n      } else {\n        this.zoomFit(true);\n      }\n    }\n  }\n\n  escapeHtml(text) {\n    if (!text) return '';\n    return text\n      .replace(/&/g, \"&amp;\")\n      .replace(/</g, \"&lt;\")\n      .replace(/>/g, \"&gt;\")\n      .replace(/\"/g, \"&quot;\")\n      .replace(/'/g, \"&#039;\");\n  }\n\n  // -------------------------------------------------------------------------\n  // Real-time Multi-User Remote Presence (Google Docs Style)\n  // -------------------------------------------------------------------------\n\n  updateRemoteCursor(sessionId, userName, userColor, canvasX, canvasY) {\n    if (this.app) {\n      if (sessionId === this.app.sessionId) return;\n      if (this.app.currentUser && this.app.currentUser.name === userName && this.app.currentUser.email) return;\n    }\n    let cursorData = this.remoteCursors.get(sessionId);\n    if (!cursorData) {\n      const el = document.createElement('div');\n      el.id = `remoteCursor-${sessionId}`;\n      el.className = 'remote-canvas-cursor';\n      el.innerHTML = `\n        <svg class=\"remote-cursor-arrow\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"${userColor}\" stroke=\"#ffffff\" stroke-width=\"1.5\">\n          <path d=\"M5.5 3.21V20.8c0 .45.54.67.85.35l4.86-4.86a.5.5 0 0 1 .35-.15h6.87c.45 0 .67-.54.35-.85L6.35 2.85a.5.5 0 0 0-.85.36z\"/>\n        </svg>\n        <span class=\"remote-cursor-label\" style=\"background-color: ${userColor};\">${this.escapeHtml(userName)}</span>\n      `;\n      // Mount inside stage so it translates, pans, and scales with canvas zoom!\n      this.stage.appendChild(el);\n      cursorData = { el, x: canvasX, y: canvasY, userName, userColor };\n      this.remoteCursors.set(sessionId, cursorData);\n    }\n\n    cursorData.x = canvasX;\n    cursorData.y = canvasY;\n    cursorData.el.style.left = `${Math.round(canvasX)}px`;\n    cursorData.el.style.top = `${Math.round(canvasY)}px`;\n  }\n\n  removeRemoteCursor(sessionId) {\n    const cursorData = this.remoteCursors.get(sessionId);\n    if (cursorData && cursorData.el) {\n      cursorData.el.remove();\n    }\n    this.remoteCursors.delete(sessionId);\n  }\n\n  updateRemoteNodeHighlight(sessionId, userName, userColor, nodeId) {\n    if (this.app && sessionId === this.app.sessionId) return;\n    // 1. Remove any previous highlight tag for this collaborator\n    const prev = this.remoteHighlights.get(sessionId);\n    if (prev && prev.nodeId) {\n      const prevEl = document.getElementById(`node-${prev.nodeId}`);\n      if (prevEl) {\n        prevEl.classList.remove('remote-selected-node');\n        const oldTag = prevEl.querySelector(`.remote-user-tag[data-session=\"${sessionId}\"]`);\n        if (oldTag) oldTag.remove();\n      }\n    }\n\n    if (!nodeId) {\n      this.remoteHighlights.delete(sessionId);\n      return;\n    }\n\n    // 2. Attach highlight tag to newly selected node\n    this.remoteHighlights.set(sessionId, { nodeId, userName, userColor });\n    const nodeEl = document.getElementById(`node-${nodeId}`);\n    if (nodeEl) {\n      nodeEl.classList.add('remote-selected-node');\n      nodeEl.style.setProperty('--remote-color', userColor);\n\n      let tag = nodeEl.querySelector(`.remote-user-tag[data-session=\"${sessionId}\"]`);\n      if (!tag) {\n        tag = document.createElement('div');\n        tag.className = 'remote-user-tag';\n        tag.dataset.session = sessionId;\n        tag.style.backgroundColor = userColor;\n        tag.textContent = `${userName} inspecting`;\n        nodeEl.appendChild(tag);\n      }\n    }\n  }\n\n  removeRemoteNodeHighlight(sessionId) {\n    this.updateRemoteNodeHighlight(sessionId, '', '', null);\n  }\n\n  clearRemotePresences() {\n    this.remoteCursors.forEach((c) => {\n      if (c.el) c.el.remove();\n    });\n    this.remoteCursors.clear();\n\n    this.remoteHighlights.forEach((h, sessId) => {\n      this.removeRemoteNodeHighlight(sessId);\n    });\n    this.remoteHighlights.clear();\n  }\n\n  // Find exact SVG connection wire path between two nodes (used by Situation Runner for particle glide)\n  findConnectionPath(fromId, toId) {\n    if (!this.connectionsGroup) return null;\n    let path = this.connectionsGroup.querySelector(`path[data-from-id=\"${fromId}\"][data-to-id=\"${toId}\"]`);\n    if (!path) {\n      path = this.connectionsGroup.querySelector(`path[data-from-id=\"${toId}\"][data-to-id=\"${fromId}\"]`);\n    }\n    return path;\n  }\n}\n",
    "js/documents.js": "// Bowtie RISK Cloud Documents & Drive Engine (Google Drive Style)\nclass BowtieDocumentsDrive {\n  constructor(app) {\n    this.app = app;\n    this.currentFolderId = null; // null = Root of Drive\n    this.activeSection = 'all';  // 'all', 'shared', 'recent'\n    this.viewMode = 'grid';      // 'grid' | 'list'\n    this.searchQuery = '';\n    this.initEvents();\n  }\n\n  initEvents() {\n    // Search input\n    const searchInput = document.getElementById('driveSearchInput');\n    if (searchInput) {\n      searchInput.addEventListener('input', (e) => {\n        this.searchQuery = e.target.value.toLowerCase().trim();\n        this.render();\n      });\n    }\n\n    // View toggle (Grid / List)\n    const btnToggleView = document.getElementById('btnDriveToggleView');\n    if (btnToggleView) {\n      this.updateViewToggleIcon();\n      btnToggleView.addEventListener('click', () => {\n        this.viewMode = this.viewMode === 'grid' ? 'list' : 'grid';\n        this.updateViewToggleIcon();\n        this.render();\n      });\n    }\n\n    // New Folder button\n    const btnNewFolder = document.getElementById('btnDriveNewFolder');\n    if (btnNewFolder) {\n      btnNewFolder.addEventListener('click', () => {\n        if (this.app && !this.app.isInstanceLicensedForEdit()) {\n          this.app.showUnlicensedPrompt();\n          return;\n        }\n        this.openNewFolderModal();\n      });\n    }\n\n    // New Diagram button\n    const btnNewDiag = document.getElementById('btnDriveNewDiagram');\n    if (btnNewDiag) {\n      btnNewDiag.addEventListener('click', () => {\n        if (this.app && !this.app.isInstanceLicensedForEdit()) {\n          this.app.showUnlicensedPrompt();\n          return;\n        }\n        const form = document.getElementById('newDiagramForm');\n        if (form) form.reset();\n        const chk = document.getElementById('chkAddToCurrentDiagram');\n        if (chk) {\n          chk.checked = false;\n          chk.dispatchEvent(new Event('change'));\n        }\n        const modal = document.getElementById('modalNewDiagram');\n        if (modal) modal.classList.add('show');\n      });\n    }\n\n    // Sidebar navigation tabs\n    const navItems = document.querySelectorAll('.drive-nav-item');\n    navItems.forEach(item => {\n      item.addEventListener('click', () => {\n        navItems.forEach(n => n.classList.remove('active'));\n        item.classList.add('active');\n        this.activeSection = item.dataset.section || 'all';\n        this.currentFolderId = null; // reset to root of selected section\n        this.render();\n      });\n    });\n\n    // App Version badge click (Desktop and Mobile)\n    const updateVersionDisplay = (text) => {\n      const desktopLabel = document.getElementById('appVersionString');\n      if (desktopLabel && text) desktopLabel.textContent = text;\n      const mobileLabel = document.getElementById('appMobileVersionString');\n      if (mobileLabel && text) mobileLabel.textContent = text;\n    };\n\n    const handleVersionCheck = async () => {\n      try {\n        const res = await this.app.apiFetch('/api/version');\n        if (res.ok) {\n          const data = await res.json();\n          if (data && data.display) updateVersionDisplay(data.display);\n          this.app.showNotification(`Bowtie Risk ${data.display || 'Version ' + data.version} • System Up to Date`, 'success');\n        } else {\n          this.app.showNotification('Bowtie Risk Version 1.1.1111 • System Up to Date', 'info');\n        }\n      } catch {\n        this.app.showNotification('Bowtie Risk Version 1.1.1111 • System Up to Date', 'info');\n      }\n    };\n\n    const versionEl = document.getElementById('driveAppVersion');\n    if (versionEl) {\n      versionEl.addEventListener('click', handleVersionCheck);\n    }\n    const mobileVersionEl = document.getElementById('driveMobileVersion');\n    if (mobileVersionEl) {\n      mobileVersionEl.addEventListener('click', handleVersionCheck);\n    }\n\n    // Background sync of version\n    this.app.apiFetch('/api/version').then(r => r.ok ? r.json() : null).then(data => {\n      if (data && data.display) updateVersionDisplay(data.display);\n    }).catch(() => {});\n\n    // Dismiss diagram card kebab menus when clicking outside\n    document.addEventListener('click', (e) => {\n      if (!e.target.closest('.card-kebab-wrapper')) {\n        document.querySelectorAll('.card-kebab-menu.show').forEach(m => m.classList.remove('show'));\n      }\n    });\n\n    // New Folder Form submission\n    const newFolderForm = document.getElementById('newFolderForm');\n    if (newFolderForm) {\n      newFolderForm.addEventListener('submit', async (e) => {\n        e.preventDefault();\n        const nameInput = document.getElementById('folderNameInput');\n        const colorInput = document.getElementById('folderColorInput');\n        const name = nameInput ? nameInput.value.trim() : '';\n        const color = colorInput ? colorInput.value : '#2563eb';\n\n        if (!name) {\n          this.app.showNotification('Please enter a folder name', 'warning');\n          if (nameInput) {\n            nameInput.classList.add('input-error');\n            nameInput.focus();\n          }\n          return;\n        }\n\n        try {\n          const res = await this.app.apiFetch('/api/folders', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({\n              name,\n              parent_id: this.currentFolderId || undefined,\n              color,\n            }),\n          });\n\n          if (res.ok) {\n            const folder = await res.json();\n            if (!this.app.workspace.folders) this.app.workspace.folders = [];\n            this.app.workspace.folders.push(folder);\n\n            if (nameInput) nameInput.classList.remove('input-error');\n            document.getElementById('modalNewFolder').classList.remove('show');\n            newFolderForm.reset();\n            this.app.showNotification(`Folder '${name}' created!`, 'success');\n            this.render();\n          } else {\n            let errMsg = 'Failed to create folder';\n            try {\n              const errJson = await res.json();\n              errMsg = errJson.message || errJson.error || errMsg;\n            } catch (_) {\n              const errText = await res.text().catch(() => '');\n              if (errText) errMsg = errText;\n            }\n            this.app.showNotification(errMsg, 'error');\n          }\n        } catch (err) {\n          this.app.showNotification('Network error: ' + err.message, 'error');\n        }\n      });\n    }\n\n    // Move Diagram Form submission\n    const moveDiagForm = document.getElementById('moveDiagramForm');\n    if (moveDiagForm) {\n      moveDiagForm.addEventListener('submit', async (e) => {\n        e.preventDefault();\n        const diagId = document.getElementById('moveDiagramIdInput').value;\n        const targetFolderSelect = document.getElementById('moveTargetFolderSelect');\n        const targetVal = targetFolderSelect ? targetFolderSelect.value : '';\n        if (!targetVal) {\n          this.app.showNotification('Please select a destination folder', 'warning');\n          if (targetFolderSelect) {\n            targetFolderSelect.classList.add('input-error');\n            targetFolderSelect.focus();\n          }\n          return;\n        }\n        const targetFolderId = targetVal === 'root' ? null : targetVal;\n\n        try {\n          const res = await this.app.apiFetch('/api/folders/move_diagram', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({\n              diagram_id: diagId,\n              folder_id: targetFolderId,\n            }),\n          });\n\n          if (res.ok) {\n            // Update local diagram\n            const d = this.app.workspace.diagrams.find(diag => diag.id === diagId);\n            if (d) d.folder_id = targetFolderId;\n\n            document.getElementById('modalMoveDiagram').classList.remove('show');\n            this.app.showNotification('Diagram moved successfully!', 'success');\n            this.render();\n          } else {\n            let errMsg = 'Failed to move diagram';\n            try {\n              const errJson = await res.json();\n              errMsg = errJson.message || errJson.error || errMsg;\n            } catch (_) {\n              const errText = await res.text().catch(() => '');\n              if (errText) errMsg = errText;\n            }\n            this.app.showNotification(errMsg, 'error');\n          }\n        } catch (err) {\n          this.app.showNotification('Error moving diagram: ' + err.message, 'error');\n        }\n      });\n    }\n  }\n\n  openNewFolderModal() {\n    if (this.app && !this.app.isInstanceLicensedForEdit()) {\n      this.app.showUnlicensedPrompt();\n      return;\n    }\n    const modal = document.getElementById('modalNewFolder');\n    if (modal) {\n      const parentLabel = document.getElementById('newFolderParentLabel');\n      if (parentLabel) {\n        const curFolder = this.getCurrentFolder();\n        parentLabel.textContent = curFolder ? `Inside: ${curFolder.name}` : 'Inside: Root Drive';\n      }\n      modal.classList.add('show');\n    }\n  }\n\n  getCurrentFolder() {\n    if (!this.currentFolderId || !this.app.workspace || !this.app.workspace.folders) return null;\n    return this.app.workspace.folders.find(f => f.id === this.currentFolderId);\n  }\n\n  getFolderAncestors(folderId) {\n    const ancestors = [];\n    let cur = folderId;\n    const all = this.app.workspace.folders || [];\n    const visited = new Set();\n\n    while (cur && !visited.has(cur)) {\n      visited.add(cur);\n      const f = all.find(x => x.id === cur);\n      if (f) {\n        ancestors.unshift(f);\n        cur = f.parent_id;\n      } else {\n        break;\n      }\n    }\n    return ancestors;\n  }\n\n  updateViewToggleIcon() {\n    const btnToggleView = document.getElementById('btnDriveToggleView');\n    if (!btnToggleView) return;\n    if (this.viewMode === 'grid') {\n      // Currently displaying tiles/grid -> show List icon to switch to list view\n      btnToggleView.title = 'Switch to List View';\n      btnToggleView.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/></svg>`;\n    } else {\n      // Currently displaying list -> show Tile/Grid icon to switch to grid view\n      btnToggleView.title = 'Switch to Grid View';\n      btnToggleView.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/></svg>`;\n    }\n  }\n\n  render() {\n    const ws = this.app.workspace;\n    if (!ws) return;\n\n    const licensed = !this.app || this.app.isInstanceLicensedForEdit();\n    const btnNewDiag = document.getElementById('btnDriveNewDiagram');\n    const btnNewFolder = document.getElementById('btnDriveNewFolder');\n    if (btnNewDiag) {\n      btnNewDiag.style.opacity = licensed ? '' : '0.6';\n      btnNewDiag.title = licensed ? 'Create a new Bowtie diagram' : 'Unlicensed On-Premise Mode: Activate license key in Admin Console to create new diagrams';\n    }\n    if (btnNewFolder) {\n      btnNewFolder.style.opacity = licensed ? '' : '0.6';\n      btnNewFolder.title = licensed ? 'Create a new folder in current location' : 'Unlicensed On-Premise Mode: Activate license key in Admin Console to create folders';\n    }\n\n    this.updateViewToggleIcon();\n    this.renderBreadcrumbs();\n    this.renderFolders();\n    this.renderDiagrams();\n    this.updateStats();\n  }\n\n  renderBreadcrumbs() {\n    const container = document.getElementById('driveBreadcrumbs');\n    if (!container) return;\n\n    container.innerHTML = '';\n    const rootItem = document.createElement('span');\n    rootItem.className = `breadcrumb-item ${this.currentFolderId === null ? 'active' : ''}`;\n    let rootLabel = 'Cloud Drive';\n    if (this.activeSection === 'shared') rootLabel = 'Shared with Me';\n    else if (this.activeSection === 'recent') rootLabel = 'Recent Activity';\n    rootItem.innerHTML = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" style=\"margin-right: 4px; vertical-align: middle;\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/></svg>${rootLabel}`;\n    rootItem.onclick = () => {\n      this.currentFolderId = null;\n      this.render();\n    };\n    container.appendChild(rootItem);\n\n    if (this.currentFolderId) {\n      const ancestors = this.getFolderAncestors(this.currentFolderId);\n      ancestors.forEach((f, idx) => {\n        const sep = document.createElement('span');\n        sep.className = 'breadcrumb-separator';\n        sep.textContent = ' / ';\n        container.appendChild(sep);\n\n        const item = document.createElement('span');\n        const isLast = idx === ancestors.length - 1;\n        item.className = `breadcrumb-item ${isLast ? 'active' : ''}`;\n        item.textContent = f.name;\n        if (!isLast) {\n          item.onclick = () => {\n            this.currentFolderId = f.id;\n            this.render();\n          };\n        }\n        container.appendChild(item);\n      });\n    }\n  }\n\n  getAccessContext() {\n    const ws = (this.app && this.app.workspace) ? this.app.workspace : {};\n    const allFolders = ws.folders || [];\n    const allDiagrams = ws.diagrams || [];\n    const perms = ws.permissions || [];\n    const userEmail = (this.app && this.app.currentUser && this.app.currentUser.email)\n      ? this.app.currentUser.email.toLowerCase()\n      : '';\n\n    const sharedFolderIds = new Set(\n      perms.filter(p => p.target_type === 'folder' && p.user_email && p.user_email.toLowerCase() === userEmail)\n           .map(p => p.target_id)\n    );\n    const sharedDiagIds = new Set(\n      perms.filter(p => p.target_type === 'diagram' && p.user_email && p.user_email.toLowerCase() === userEmail)\n           .map(p => p.target_id)\n    );\n\n    const isOwnedFolder = (f) => !userEmail || !!(f.created_by && f.created_by.toLowerCase() === userEmail);\n    const ownedFolderIds = new Set(allFolders.filter(isOwnedFolder).map(f => f.id));\n    const hasFolderAccess = (f) => isOwnedFolder(f) || sharedFolderIds.has(f.id);\n\n    const isOwnedDiagram = (d) => !userEmail || !!(d.created_by && d.created_by.toLowerCase() === userEmail);\n    const hasDiagramAccess = (d) => isOwnedDiagram(d) || sharedDiagIds.has(d.id) || !!(d.folder_id && (ownedFolderIds.has(d.folder_id) || sharedFolderIds.has(d.folder_id)));\n\n    return {\n      allFolders,\n      allDiagrams,\n      perms,\n      userEmail,\n      sharedFolderIds,\n      sharedDiagIds,\n      ownedFolderIds,\n      isOwnedFolder,\n      hasFolderAccess,\n      isOwnedDiagram,\n      hasDiagramAccess,\n    };\n  }\n\n  renderFolders() {\n    const container = document.getElementById('driveFoldersGrid');\n    const section = document.getElementById('driveFoldersSection');\n    if (!container || !section) return;\n\n    const {\n      allFolders,\n      allDiagrams,\n      perms,\n      userEmail,\n      sharedFolderIds,\n      isOwnedFolder,\n      hasFolderAccess,\n      isOwnedDiagram,\n      hasDiagramAccess,\n    } = this.getAccessContext();\n    let folders = [];\n\n    if (this.activeSection === 'all') {\n      if (this.currentFolderId === null) {\n        folders = allFolders.filter(f => \n          (f.parent_id || null) === null &&\n          isOwnedFolder(f)\n        );\n      } else {\n        folders = allFolders.filter(f => (f.parent_id || null) === this.currentFolderId && isOwnedFolder(f));\n      }\n    } else if (this.activeSection === 'shared') {\n      // Folders shared with current user\n      if (this.currentFolderId === null) {\n        folders = allFolders.filter(f => sharedFolderIds.has(f.id) && (!userEmail || !f.created_by || f.created_by.toLowerCase() !== userEmail));\n      } else {\n        folders = allFolders.filter(f => (f.parent_id || null) === this.currentFolderId && hasFolderAccess(f));\n      }\n    } else if (this.activeSection === 'recent') {\n      // Show top recent folders accessible to user\n      folders = allFolders.filter(f => hasFolderAccess(f)).slice().sort((a, b) => (b.updated_at || '').localeCompare(a.updated_at || '')).slice(0, 4);\n    }\n\n    if (this.searchQuery) {\n      folders = folders.filter(f => f.name.toLowerCase().includes(this.searchQuery));\n    }\n\n    container.innerHTML = '';\n    if (folders.length === 0) {\n      if (this.currentFolderId === null && !this.searchQuery && this.activeSection === 'all') {\n        container.innerHTML = `<div class=\"drive-empty-hint\">No folders created yet. Click \"+ New Folder\" to organize your diagrams.</div>`;\n      } else {\n        section.style.display = folders.length > 0 ? 'block' : 'none';\n        return;\n      }\n    }\n    section.style.display = 'block';\n\n    folders.forEach(f => {\n      const diagCount = allDiagrams.filter(d =>\n        d.folder_id === f.id &&\n        (this.activeSection === 'shared' ? hasDiagramAccess(d) : (isOwnedDiagram(d) || isOwnedFolder(f)))\n      ).length;\n      const isShared = perms.some(p => p.target_type === 'folder' && p.target_id === f.id);\n\n      const card = document.createElement('div');\n      card.className = 'drive-folder-card';\n      const color = f.color || '#2563eb';\n\n      card.innerHTML = `\n        <div class=\"folder-card-main\">\n          <div class=\"folder-icon-wrap\" style=\"color: ${color};\">\n            <svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/></svg>\n          </div>\n          <div class=\"folder-meta\">\n            <div class=\"folder-name\" title=\"${this.escapeHtml(f.name)}\">${this.escapeHtml(f.name)}</div>\n            <div class=\"folder-count\">${diagCount} diagram${diagCount === 1 ? '' : 's'}</div>\n          </div>\n          ${isShared ? `<span class=\"badge badge-shared\" title=\"Shared with team\">Shared</span>` : ''}\n          <div class=\"folder-actions-btn\" title=\"Folder actions\">⋮</div>\n        </div>\n      `;\n\n      // Click card to open folder\n      card.addEventListener('click', (e) => {\n        if (e.target.closest('.folder-actions-btn')) {\n          e.stopPropagation();\n          this.showFolderContextMenu(f, e.target);\n          return;\n        }\n        this.currentFolderId = f.id;\n        this.render();\n      });\n\n      container.appendChild(card);\n    });\n  }\n\n  renderDiagrams() {\n    const gridContainer = document.getElementById('driveDiagramsGrid');\n    const listContainer = document.getElementById('driveDiagramsList');\n    const tableBody = document.getElementById('driveDiagramsTableBody');\n    if (!gridContainer || !listContainer) return;\n\n    if (this.viewMode === 'grid') {\n      gridContainer.style.display = 'grid';\n      listContainer.style.display = 'none';\n    } else {\n      gridContainer.style.display = 'none';\n      listContainer.style.display = 'block';\n    }\n\n    const {\n      allDiagrams,\n      sharedDiagIds,\n      sharedFolderIds,\n      ownedFolderIds,\n      isOwnedDiagram,\n      hasDiagramAccess,\n    } = this.getAccessContext();\n    const perms = this.app.workspace.permissions || [];\n    const userEmail = (this.app.currentUser && this.app.currentUser.email) ? this.app.currentUser.email.toLowerCase() : '';\n    let diagrams = [];\n\n    if (this.activeSection === 'all') {\n      if (this.currentFolderId === null) {\n        diagrams = allDiagrams.filter(d => \n          (d.folder_id || null) === null &&\n          isOwnedDiagram(d)\n        );\n      } else {\n        const isCurrentFolderOwned = ownedFolderIds.has(this.currentFolderId);\n        diagrams = allDiagrams.filter(d =>\n          (d.folder_id || null) === this.currentFolderId &&\n          (isOwnedDiagram(d) || isCurrentFolderOwned)\n        );\n      }\n    } else if (this.activeSection === 'shared') {\n      if (this.currentFolderId === null) {\n        diagrams = allDiagrams.filter(d => \n          (sharedDiagIds.has(d.id) || (d.folder_id && sharedFolderIds.has(d.folder_id))) &&\n          (!userEmail || !d.created_by || d.created_by.toLowerCase() !== userEmail)\n        );\n      } else {\n        diagrams = allDiagrams.filter(d => (d.folder_id || null) === this.currentFolderId && hasDiagramAccess(d));\n      }\n    } else if (this.activeSection === 'recent') {\n      diagrams = allDiagrams.filter(d => hasDiagramAccess(d)).slice().sort((a, b) => (b.updated_at || '').localeCompare(a.updated_at || ''));\n    }\n\n    if (this.searchQuery) {\n      diagrams = diagrams.filter(d => \n        d.title.toLowerCase().includes(this.searchQuery) ||\n        (d.description && d.description.toLowerCase().includes(this.searchQuery))\n      );\n    }\n\n    gridContainer.innerHTML = '';\n    if (tableBody) tableBody.innerHTML = '';\n\n    if (diagrams.length === 0) {\n      let emptyTitle = 'No Bowtie diagrams in this view';\n      let emptyMsg = 'Click \"+ New Diagram\" above to create a risk assessment.';\n      if (this.activeSection === 'shared') {\n        emptyTitle = 'No diagrams shared with you yet';\n        emptyMsg = 'Diagrams shared by colleagues with your account will appear here.';\n      } else if (this.activeSection === 'recent') {\n        emptyTitle = 'No recent activity';\n        emptyMsg = 'Diagrams you recently viewed or edited will appear here.';\n      }\n      gridContainer.innerHTML = `\n        <div class=\"drive-empty-card\">\n          <svg width=\"48\" height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#94a3b8\" stroke-width=\"1.5\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>\n          <h4>${emptyTitle}</h4>\n          <p class=\"text-sm text-muted\">${emptyMsg}</p>\n        </div>\n      `;\n      if (tableBody) {\n        tableBody.innerHTML = `<tr><td colspan=\"6\" class=\"text-center text-muted\" style=\"padding: 24px;\">${emptyTitle}.</td></tr>`;\n      }\n      return;\n    }\n\n    const nodes = this.app.workspace.nodes || [];\n    const folders = this.app.workspace.folders || [];\n\n    diagrams.forEach(diag => {\n      const diagNodes = nodes.filter(n => n.diagram_id === diag.id);\n      const hazard = diagNodes.find(n => n.node_type === 'hazard');\n      const topEvent = diagNodes.find(n => n.node_type === 'top_event');\n      const threatsCount = diagNodes.filter(n => n.node_type === 'threat').length;\n      const barriersCount = diagNodes.filter(n => n.node_type === 'threat_barrier' || n.node_type === 'outcome_barrier').length;\n      const outcomesCount = diagNodes.filter(n => n.node_type === 'outcome').length;\n      const folder = folders.find(f => f.id === diag.folder_id);\n      const isShared = perms.some(p => p.target_type === 'diagram' && p.target_id === diag.id);\n\n      const updatedDate = diag.updated_at ? new Date(diag.updated_at).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) : 'Recently';\n\n      // Directory profile resolution for document creator\n      const creatorEmailLower = (diag.created_by || '').toLowerCase();\n      const creatorUser = this.app.getUserByEmail(diag.created_by) ||\n                          (this.app.directoryUsersMap && this.app.directoryUsersMap[creatorEmailLower]) ||\n                          (this.app.workspace && this.app.workspace.users || []).find(u => u.email.toLowerCase() === creatorEmailLower) ||\n                          (this.app.currentUser && this.app.currentUser.email.toLowerCase() === creatorEmailLower ? this.app.currentUser : null);\n\n      const creatorName = creatorUser ? (creatorUser.name || diag.created_by) : (diag.created_by || 'Me');\n      const creatorAvatarHtml = this.app.getUserAvatarHtml(creatorUser || { email: diag.created_by, name: creatorName }, 26);\n\n      // Shared collaborator avatars for document access\n      const diagPerms = perms.filter(p => p.target_type === 'diagram' && p.target_id === diag.id);\n      let collabDotsHtml = '';\n      if (diagPerms.length > 0) {\n        collabDotsHtml = '<div class=\"collaborators-avatars-mini\" title=\"Shared with team\">';\n        diagPerms.slice(0, 3).forEach(p => {\n          const u = this.app.getUserByEmail(p.user_email) ||\n                    (this.app.directoryUsersMap && this.app.directoryUsersMap[p.user_email.toLowerCase()]) ||\n                    (this.app.workspace && this.app.workspace.users || []).find(x => x.email.toLowerCase() === p.user_email.toLowerCase()) ||\n                    { email: p.user_email, name: p.user_email };\n          const name = u ? (u.name || p.user_email) : p.user_email;\n          const roleLabel = (p.access_level || 'Viewer').toUpperCase();\n          const tooltip = `${name} (${p.user_email}) - ${roleLabel}`;\n          collabDotsHtml += `<span class=\"collaborator-avatar-dot\" title=\"${this.escapeHtml(tooltip)}\">${this.app.getUserAvatarHtml(u, 22)}</span>`;\n        });\n        if (diagPerms.length > 3) {\n          collabDotsHtml += `<span class=\"collaborator-avatar-dot collaborator-more-count\" style=\"background: #475569; color: #ffffff; font-size: 8.5px;\" title=\"+${diagPerms.length - 3} more colleagues\">+${diagPerms.length - 3}</span>`;\n        }\n        collabDotsHtml += '</div>';\n      }\n\n      // Extract meaningful actual Hazard and Top Event descriptions/titles\n      const getMeaningfulText = (node, defaultLabel) => {\n        if (!node) return '';\n        const d = (node.description || '').trim();\n        const displayTitle = (this.app ? this.app.getNodeDisplayTitle(node) : (node.title || '')).trim();\n        const t = (node.title || '').trim();\n        const def = defaultLabel.toLowerCase();\n        if (d && d.toLowerCase() !== def && d.toLowerCase() !== 'hazard' && d.toLowerCase() !== 'top event') return d;\n        if (displayTitle && displayTitle.toLowerCase() !== def && displayTitle.toLowerCase() !== 'hazard' && displayTitle.toLowerCase() !== 'top event') return displayTitle;\n        if (t && t.toLowerCase() !== def && t.toLowerCase() !== 'hazard' && t.toLowerCase() !== 'top event') return t;\n        return d || displayTitle || t || defaultLabel;\n      };\n\n      const hazardText = getMeaningfulText(hazard, 'Hazard');\n      const topEventText = getMeaningfulText(topEvent, 'Top event');\n\n      const customNames = this.app ? this.app.getCustomNames() : {};\n      const threatsTitle = customNames['threat'] || 'Threats';\n      const barriersTitle = (customNames['threat_barrier'] && customNames['outcome_barrier']) ? 'Barriers' : (customNames['threat_barrier'] || 'Barriers');\n      const outcomesTitle = customNames['outcome'] || 'Outcomes';\n\n      // 1. Grid Card\n      const thumbnailSvg = this.generateDiagramThumbnailSvg(diag, diagNodes, hazardText, topEventText);\n      const card = document.createElement('div');\n      card.className = 'drive-diagram-card';\n      card.innerHTML = `\n        <div class=\"diagram-card-preview\">\n          ${thumbnailSvg}\n          <div class=\"diagram-card-preview-overlay\">\n            ${folder ? `<span class=\"card-folder-tag\" style=\"background: ${folder.color || '#2563eb'}20; color: ${folder.color || '#2563eb'};\">📁 ${this.escapeHtml(folder.name)}</span>` : ''}\n            ${isShared ? `<span class=\"badge badge-shared\">Shared</span>` : ''}\n          </div>\n        </div>\n        <div class=\"diagram-card-body\">\n          <h4 class=\"diagram-card-title\" title=\"${this.escapeHtml(diag.title)}\">${this.escapeHtml(diag.title)}</h4>\n          ${diag.description ? `<p class=\"diagram-card-desc\" title=\"${this.escapeHtml(diag.description)}\">${this.escapeHtml(diag.description)}</p>` : ''}\n          \n          <div class=\"diagram-card-keynodes\">\n            ${hazardText ? `<div class=\"keynode-pill hazard-pill\" title=\"Hazard: ${this.escapeHtml(hazardText)}\">⚡ ${this.escapeHtml(hazardText)}</div>` : ''}\n            ${topEventText ? `<div class=\"keynode-pill topevent-pill\" title=\"Top Event: ${this.escapeHtml(topEventText)}\">🎯 ${this.escapeHtml(topEventText)}</div>` : ''}\n          </div>\n\n          <!-- Threats, Outcomes, Barriers are UNDER the Top Event at the bottom of the panel just above user information -->\n          <div class=\"diagram-card-stats\">\n            <span class=\"stat-pill stat-threats\" title=\"${this.escapeHtml(threatsTitle)}\">${threatsCount} ${this.escapeHtml(threatsTitle)}</span>\n            <span class=\"stat-pill stat-barriers\" title=\"${this.escapeHtml(barriersTitle)}\">${barriersCount} ${this.escapeHtml(barriersTitle)}</span>\n            <span class=\"stat-pill stat-outcomes\" title=\"${this.escapeHtml(outcomesTitle)}\">${outcomesCount} ${this.escapeHtml(outcomesTitle)}</span>\n          </div>\n        </div>\n        <div class=\"diagram-card-footer\">\n          <div class=\"author-meta\" style=\"display: flex; align-items: center; gap: 8px; min-width: 0;\">\n            <div class=\"author-avatar-wrap\" title=\"Created by ${this.escapeHtml(creatorName)} (${this.escapeHtml(diag.created_by || '')})\">${creatorAvatarHtml}</div>\n            <div style=\"min-width: 0;\">\n              <span class=\"text-xs\" style=\"font-weight: 600; color: #1e293b; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100px;\">${this.escapeHtml(creatorName)}</span>\n              <span class=\"text-xs text-muted\" style=\"display: block; font-size: 11px;\">${updatedDate}</span>\n            </div>\n            ${collabDotsHtml}\n          </div>\n          <div class=\"card-kebab-wrapper\">\n            <button type=\"button\" class=\"btn-card-kebab\" title=\"Assessment options\">\n              <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><circle cx=\"12\" cy=\"5\" r=\"1.5\"/><circle cx=\"12\" cy=\"12\" r=\"1.5\"/><circle cx=\"12\" cy=\"19\" r=\"1.5\"/></svg>\n            </button>\n            <div class=\"card-kebab-menu\">\n              <button type=\"button\" class=\"dropdown-item btn-kebab-copy btn-card-copy\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>\n                <span>Make a Copy</span>\n              </button>\n              <button type=\"button\" class=\"dropdown-item btn-kebab-rename btn-card-rename\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/></svg>\n                <span>Rename</span>\n              </button>\n              <button type=\"button\" class=\"dropdown-item btn-kebab-share\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"/><polyline points=\"16 6 12 2 8 6\"/><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"15\"/></svg>\n                <span>Share with Team</span>\n              </button>\n              <button type=\"button\" class=\"dropdown-item btn-kebab-move\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/></svg>\n                <span>Move to Folder</span>\n              </button>\n              <button type=\"button\" class=\"dropdown-item btn-kebab-export\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>\n                <span>Export JSON</span>\n              </button>\n              <hr class=\"dropdown-divider\" style=\"margin: 4px 0;\">\n              <button type=\"button\" class=\"dropdown-item btn-kebab-delete\" style=\"color: #ef4444;\">\n                <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg>\n                <span>Delete Assessment</span>\n              </button>\n            </div>\n          </div>\n        </div>\n      `;\n\n      // Click card to open in canvas editor (unless clicking kebab or collaborators)\n      card.addEventListener('click', (e) => {\n        if (e.target.closest('.card-kebab-wrapper') || e.target.closest('.collaborators-avatars-mini')) {\n          return;\n        }\n        this.openDiagramInCanvas(diag.id);\n      });\n\n      // 7.3: Clicking people shared with opens sharing popup with diagram info\n      const collabAvatarsEl = card.querySelector('.collaborators-avatars-mini');\n      if (collabAvatarsEl) {\n        collabAvatarsEl.onclick = (e) => {\n          e.stopPropagation();\n          this.openShareForDiagram(diag);\n        };\n      }\n\n      // 7.1: 3-dot kebab menu actions\n      const kebabBtn = card.querySelector('.btn-card-kebab');\n      const kebabMenu = card.querySelector('.card-kebab-menu');\n      if (kebabBtn && kebabMenu) {\n        kebabBtn.onclick = (e) => {\n          e.stopPropagation();\n          document.querySelectorAll('.card-kebab-menu.show').forEach(m => {\n            if (m !== kebabMenu) m.classList.remove('show');\n          });\n          kebabMenu.classList.toggle('show');\n        };\n\n        const copyBtn = card.querySelector('.btn-kebab-copy');\n        if (copyBtn) {\n          copyBtn.onclick = (e) => {\n            e.stopPropagation();\n            kebabMenu.classList.remove('show');\n            this.copyDiagram(diag.id);\n          };\n        }\n\n        const renameBtn = card.querySelector('.btn-kebab-rename');\n        if (renameBtn) {\n          renameBtn.onclick = (e) => {\n            e.stopPropagation();\n            kebabMenu.classList.remove('show');\n            this.renameDiagram(diag.id);\n          };\n        }\n\n        const shareBtn = card.querySelector('.btn-kebab-share');\n        if (shareBtn) {\n          shareBtn.onclick = (e) => {\n            e.stopPropagation();\n            kebabMenu.classList.remove('show');\n            this.openShareForDiagram(diag);\n          };\n        }\n\n        const moveBtn = card.querySelector('.btn-kebab-move');\n        if (moveBtn) {\n          moveBtn.onclick = (e) => {\n            e.stopPropagation();\n            kebabMenu.classList.remove('show');\n            this.openMoveModalForDiagram(diag);\n          };\n        }\n\n        const exportBtn = card.querySelector('.btn-kebab-export');\n        if (exportBtn) {\n          exportBtn.onclick = (e) => {\n            e.stopPropagation();\n            kebabMenu.classList.remove('show');\n            this.exportDiagramJson(diag.id);\n          };\n        }\n\n        const deleteBtn = card.querySelector('.btn-kebab-delete');\n        if (deleteBtn) {\n          deleteBtn.onclick = (e) => {\n            e.stopPropagation();\n            kebabMenu.classList.remove('show');\n            this.deleteDiagram(diag.id);\n          };\n        }\n      }\n\n      // Double click card to open\n      card.ondblclick = () => this.openDiagramInCanvas(diag.id);\n\n      gridContainer.appendChild(card);\n\n      // 2. List View Row\n      if (tableBody) {\n        const tr = document.createElement('tr');\n        tr.innerHTML = `\n          <td>\n            <div class=\"table-doc-title\">\n              <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2563eb\" stroke-width=\"2\" style=\"margin-right: 8px;\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>\n              <strong>${this.escapeHtml(diag.title)}</strong>\n            </div>\n          </td>\n          <td>${folder ? `<span class=\"badge\" style=\"background: ${folder.color || '#2563eb'}20; color: ${folder.color || '#2563eb'};\">${this.escapeHtml(folder.name)}</span>` : '<span class=\"text-muted text-xs\">Root Drive</span>'}</td>\n          <td>\n            <div style=\"display: flex; align-items: center; gap: 8px;\">\n              <div class=\"author-avatar-wrap\" style=\"width: 24px; height: 24px;\" title=\"${this.escapeHtml(creatorName)} (${this.escapeHtml(diag.created_by || '')})\">\n                ${this.app.getUserAvatarHtml(creatorUser || { email: diag.created_by, name: creatorName }, 24)}\n              </div>\n              <span style=\"font-weight: 500; font-size: 13px;\">${this.escapeHtml(creatorName)}</span>\n              ${collabDotsHtml}\n            </div>\n          </td>\n          <td><span class=\"badge\">${diagNodes.length} nodes</span></td>\n          <td class=\"text-xs text-muted\">${updatedDate}</td>\n          <td>\n            <div class=\"row-actions\">\n              <button class=\"btn btn-xs btn-primary btn-row-open\">Open</button>\n              <button class=\"btn btn-xs btn-outline btn-row-copy\">Copy</button>\n              <button class=\"btn btn-xs btn-outline btn-row-rename\">Rename</button>\n              <button class=\"btn btn-xs btn-outline btn-row-share\">Share</button>\n              <button class=\"btn btn-xs btn-outline btn-row-move\">Move</button>\n            </div>\n          </td>\n        `;\n\n        tr.querySelector('.btn-row-open').onclick = () => this.openDiagramInCanvas(diag.id);\n        tr.querySelector('.btn-row-copy').onclick = (e) => {\n          e.stopPropagation();\n          this.copyDiagram(diag.id);\n        };\n        tr.querySelector('.btn-row-rename').onclick = (e) => {\n          e.stopPropagation();\n          this.renameDiagram(diag.id);\n        };\n        tr.querySelector('.btn-row-share').onclick = (e) => {\n          e.stopPropagation();\n          this.openShareForDiagram(diag);\n        };\n        tr.querySelector('.btn-row-move').onclick = (e) => {\n          e.stopPropagation();\n          this.openMoveModalForDiagram(diag);\n        };\n        tr.ondblclick = () => this.openDiagramInCanvas(diag.id);\n\n        const rowCollabAvatarsEl = tr.querySelector('.collaborators-avatars-mini');\n        if (rowCollabAvatarsEl) {\n          rowCollabAvatarsEl.onclick = (e) => {\n            e.stopPropagation();\n            this.openShareForDiagram(diag);\n          };\n        }\n\n        tableBody.appendChild(tr);\n      }\n    });\n  }\n\n  copyDiagram(diagId) {\n    if (this.app && !this.app.isInstanceLicensedForEdit()) {\n      this.app.showUnlicensedPrompt();\n      return;\n    }\n    const diag = this.app.workspace.diagrams.find(d => d.id === diagId);\n    if (!diag) return;\n\n    const newDiagId = `diag-${Date.now().toString(36)}`;\n    const now = new Date().toISOString();\n    const sourceNodes = (this.app.workspace.nodes || []).filter(n => n.diagram_id === diagId);\n\n    // Map old node ID to new node ID\n    const idMap = new Map();\n    sourceNodes.forEach(n => {\n      idMap.set(n.id, `node-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 6)}`);\n    });\n\n    const clonedNodes = sourceNodes.map(n => {\n      const cloned = JSON.parse(JSON.stringify(n));\n      cloned.id = idMap.get(n.id);\n      cloned.diagram_id = newDiagId;\n      if (cloned.parent_id && idMap.has(cloned.parent_id)) {\n        cloned.parent_id = idMap.get(cloned.parent_id);\n      }\n      cloned.created_at = now;\n      cloned.updated_at = now;\n      return cloned;\n    });\n\n    const newHazardId = diag.hazard_id ? idMap.get(diag.hazard_id) : null;\n    const newTopEventId = diag.top_event_id ? idMap.get(diag.top_event_id) : null;\n\n    const clonedDiag = {\n      ...JSON.parse(JSON.stringify(diag)),\n      id: newDiagId,\n      title: `${diag.title} (Copy)`,\n      hazard_id: newHazardId,\n      top_event_id: newTopEventId,\n      created_by: this.app.currentUser ? this.app.currentUser.email : diag.created_by,\n      created_at: now,\n      updated_at: now\n    };\n\n    this.app.workspace.diagrams.push(clonedDiag);\n    this.app.workspace.nodes.push(...clonedNodes);\n    this.app.undoStacks[newDiagId] = [];\n    this.app.redoStacks[newDiagId] = [];\n\n    this.render();\n    this.app.initDiagramSelector();\n    this.app.renderDiagramTabs();\n    this.app.showNotification(`Created copy '${clonedDiag.title}'!`, 'success');\n    this.app.scheduleAutoSave(`Duplicated diagram ${diag.title}`);\n  }\n\n  renameDiagram(diagId) {\n    if (this.app && !this.app.isInstanceLicensedForEdit()) {\n      this.app.showUnlicensedPrompt();\n      return;\n    }\n    const diag = this.app.workspace.diagrams.find(d => d.id === diagId);\n    if (!diag) return;\n\n    const newTitle = prompt('Enter new assessment title:', diag.title);\n    if (newTitle === null) return;\n    const trimmed = newTitle.trim();\n    if (!trimmed) {\n      this.app.showNotification('Diagram title cannot be empty.', 'warning');\n      return;\n    }\n\n    diag.title = trimmed;\n    diag.updated_at = new Date().toISOString();\n\n    if (this.app.currentDiagram && this.app.currentDiagram.id === diagId) {\n      this.app.currentDiagram.title = trimmed;\n      const titleEl = document.getElementById('currentDiagramTitle');\n      if (titleEl) titleEl.textContent = trimmed;\n    }\n\n    this.render();\n    this.app.initDiagramSelector();\n    this.app.renderDiagramTabs();\n    this.app.showNotification(`Renamed assessment to '${trimmed}'!`, 'success');\n    this.app.scheduleAutoSave(`Renamed diagram to ${trimmed}`);\n  }\n\n  async deleteDiagram(diagId) {\n    if (this.app && !this.app.isInstanceLicensedForEdit()) {\n      this.app.showUnlicensedPrompt();\n      return;\n    }\n    const diag = this.app.workspace.diagrams.find(d => d.id === diagId);\n    if (!diag) return;\n\n    if (!confirm(`Are you sure you want to delete assessment '${diag.title}'? This action cannot be undone.`)) {\n      return;\n    }\n\n    try {\n      const res = await this.app.apiFetch(`/api/diagrams/${encodeURIComponent(diagId)}`, {\n        method: 'DELETE',\n      });\n      if (!res.ok && res.status !== 404) {\n        let errMsg = 'Failed to delete diagram';\n        try {\n          const errJson = await res.json();\n          errMsg = errJson.error || errJson.message || errMsg;\n        } catch (_) {}\n        this.app.showNotification(errMsg, 'error');\n        return;\n      }\n    } catch (err) {\n      this.app.showNotification('Error deleting diagram: ' + err.message, 'error');\n      return;\n    }\n\n    // Close tab if open\n    if (this.app.openDiagramIds && this.app.openDiagramIds.includes(diagId)) {\n      this.app.closeDiagram(diagId);\n    }\n\n    // Remove diagram and its nodes from workspace\n    this.app.workspace.diagrams = (this.app.workspace.diagrams || []).filter(d => d.id !== diagId);\n    this.app.workspace.nodes = (this.app.workspace.nodes || []).filter(n => n.diagram_id !== diagId);\n    if (this.app.workspace.permissions) {\n      this.app.workspace.permissions = this.app.workspace.permissions.filter(p => !(p.target_type === 'diagram' && p.target_id === diagId));\n    }\n    delete this.app.undoStacks[diagId];\n    delete this.app.redoStacks[diagId];\n\n    this.render();\n    this.app.initDiagramSelector();\n    this.app.renderDiagramTabs();\n    this.app.showNotification(`Deleted assessment '${diag.title}'.`, 'info');\n  }\n\n  exportDiagramJson(diagId) {\n    const diag = this.app.workspace.diagrams.find(d => d.id === diagId);\n    if (!diag) return;\n\n    const diagNodes = (this.app.workspace.nodes || []).filter(n => n.diagram_id === diagId);\n    const exportData = {\n      version: \"1.0\",\n      exported_at: new Date().toISOString(),\n      diagram: diag,\n      nodes: diagNodes\n    };\n\n    const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });\n    const url = URL.createObjectURL(blob);\n    const a = document.createElement('a');\n    a.href = url;\n    a.download = `${diag.title.toLowerCase().replace(/[^a-z0-9]+/g, '_')}_assessment.json`;\n    document.body.appendChild(a);\n    a.click();\n    document.body.removeChild(a);\n    URL.revokeObjectURL(url);\n    this.app.showNotification(`Exported '${diag.title}' as JSON`, 'success');\n  }\n\n  updateStats() {\n    const diagCountEl = document.getElementById('driveStatsDiagCount');\n    const folderCountEl = document.getElementById('driveStatsFolderCount');\n    const {\n      allFolders,\n      allDiagrams,\n      userEmail,\n      sharedFolderIds,\n      sharedDiagIds,\n      ownedFolderIds,\n      isOwnedFolder,\n      isOwnedDiagram,\n    } = this.getAccessContext();\n\n    if (this.activeSection === 'shared') {\n      const sharedDiagCount = allDiagrams.filter(d =>\n        (sharedDiagIds.has(d.id) || (d.folder_id && sharedFolderIds.has(d.folder_id))) &&\n        (!userEmail || !d.created_by || d.created_by.toLowerCase() !== userEmail)\n      ).length;\n      const sharedFolderCount = allFolders.filter(f =>\n        sharedFolderIds.has(f.id) &&\n        (!userEmail || !f.created_by || f.created_by.toLowerCase() !== userEmail)\n      ).length;\n      if (diagCountEl) diagCountEl.textContent = `${sharedDiagCount} Diagram${sharedDiagCount === 1 ? '' : 's'}`;\n      if (folderCountEl) folderCountEl.textContent = `${sharedFolderCount} Folder${sharedFolderCount === 1 ? '' : 's'}`;\n    } else {\n      const myDiagCount = allDiagrams.filter(d =>\n        isOwnedDiagram(d) || (d.folder_id && ownedFolderIds.has(d.folder_id))\n      ).length;\n      const myFolderCount = allFolders.filter(isOwnedFolder).length;\n      if (diagCountEl) diagCountEl.textContent = `${myDiagCount} Diagram${myDiagCount === 1 ? '' : 's'}`;\n      if (folderCountEl) folderCountEl.textContent = `${myFolderCount} Folder${myFolderCount === 1 ? '' : 's'}`;\n    }\n  }\n\n  openDiagramInCanvas(diagramId) {\n    this.app.openDiagram(diagramId);\n    this.app.switchToView('canvas');\n  }\n\n  openShareForDiagram(diag) {\n    this.app.openShareModalForDiagram(diag);\n  }\n\n  openShareForFolder(folder) {\n    this.app.openShareModalForFolder(folder);\n  }\n\n  openMoveModalForDiagram(diag) {\n    if (this.app && !this.app.isInstanceLicensedForEdit()) {\n      this.app.showUnlicensedPrompt();\n      return;\n    }\n    const modal = document.getElementById('modalMoveDiagram');\n    const idInput = document.getElementById('moveDiagramIdInput');\n    const titleLabel = document.getElementById('moveDiagramTitleLabel');\n    const select = document.getElementById('moveTargetFolderSelect');\n\n    if (idInput) idInput.value = diag.id;\n    if (titleLabel) titleLabel.textContent = diag.title;\n\n    if (select) {\n      select.innerHTML = '<option value=\"root\">📁 Root of Drive</option>';\n      const userEmail = (this.app.currentUser && this.app.currentUser.email) ? this.app.currentUser.email.toLowerCase() : '';\n      const perms = this.app.workspace.permissions || [];\n      const writableFolderIds = new Set(\n        perms.filter(p => p.target_type === 'folder' && p.user_email.toLowerCase() === userEmail && (p.access_level === 'write' || p.access_level === 'admin'))\n             .map(p => p.target_id)\n      );\n      const folders = (this.app.workspace.folders || []).filter(f =>\n        (!userEmail || (f.created_by && f.created_by.toLowerCase() === userEmail)) || writableFolderIds.has(f.id)\n      );\n      folders.forEach(f => {\n        const opt = document.createElement('option');\n        opt.value = f.id;\n        opt.textContent = `📁 ${f.name}`;\n        if (diag.folder_id === f.id) opt.selected = true;\n        select.appendChild(opt);\n      });\n    }\n\n    if (modal) modal.classList.add('show');\n  }\n\n  showFolderContextMenu(folder, targetEl) {\n    // Show quick action menu\n    const existingMenu = document.getElementById('folderContextMenu');\n    if (existingMenu) existingMenu.remove();\n\n    const menu = document.createElement('div');\n    menu.id = 'folderContextMenu';\n    menu.className = 'dropdown-menu show folder-context-menu';\n\n    menu.innerHTML = `\n      <button class=\"dropdown-item btn-ctx-share\">\n        <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8\"/><polyline points=\"16 6 12 2 8 6\"/><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"15\"/></svg>\n        Share Folder & Permissions\n      </button>\n      <button class=\"dropdown-item btn-ctx-rename\">\n        <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\"/></svg>\n        Rename Folder\n      </button>\n      <hr class=\"dropdown-divider\">\n      <button class=\"dropdown-item btn-ctx-delete\" style=\"color: #dc2626;\">\n        <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg>\n        Delete Folder\n      </button>\n    `;\n\n    const rect = targetEl.getBoundingClientRect();\n    menu.style.position = 'fixed';\n    menu.style.top = `${rect.bottom + 4}px`;\n    menu.style.left = `${Math.max(10, rect.right - 180)}px`;\n    menu.style.zIndex = '9999';\n\n    menu.querySelector('.btn-ctx-share').onclick = () => {\n      menu.remove();\n      this.openShareForFolder(folder);\n    };\n\n    menu.querySelector('.btn-ctx-rename').onclick = async () => {\n      menu.remove();\n      const newName = prompt('Enter new folder name:', folder.name);\n      if (newName && newName.trim() && newName.trim() !== folder.name) {\n        try {\n          const res = await this.app.apiFetch(`/api/folders/${folder.id}`, {\n            method: 'PUT',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ name: newName.trim() }),\n          });\n          if (res.ok) {\n            folder.name = newName.trim();\n            this.render();\n            this.app.showNotification('Folder renamed!', 'success');\n          } else {\n            let errMsg = 'Failed to rename folder';\n            try {\n              const errJson = await res.json();\n              errMsg = errJson.message || errJson.error || errMsg;\n            } catch (_) {\n              const errText = await res.text().catch(() => '');\n              if (errText) errMsg = errText;\n            }\n            this.app.showNotification(errMsg, 'error');\n          }\n        } catch (e) {\n          this.app.showNotification('Error renaming folder: ' + e.message, 'error');\n        }\n      }\n    };\n\n    menu.querySelector('.btn-ctx-delete').onclick = async () => {\n      menu.remove();\n      if (confirm(`Delete folder '${folder.name}'? Diagrams inside will be moved to root.`)) {\n        try {\n          const res = await this.app.apiFetch(`/api/folders/${folder.id}`, { method: 'DELETE' });\n          if (res.ok) {\n            this.app.workspace.folders = this.app.workspace.folders.filter(f => f.id !== folder.id);\n            // Move diagrams to root\n            (this.app.workspace.diagrams || []).forEach(d => {\n              if (d.folder_id === folder.id) d.folder_id = null;\n            });\n            this.render();\n            this.app.showNotification(`Folder '${folder.name}' deleted.`, 'info');\n          } else {\n            let errMsg = 'Failed to delete folder';\n            try {\n              const errJson = await res.json();\n              errMsg = errJson.message || errJson.error || errMsg;\n            } catch (_) {\n              const errText = await res.text().catch(() => '');\n              if (errText) errMsg = errText;\n            }\n            this.app.showNotification(errMsg, 'error');\n          }\n        } catch (e) {\n          this.app.showNotification('Error deleting folder: ' + e.message, 'error');\n        }\n      }\n    };\n\n    document.body.appendChild(menu);\n\n    const onDocClick = (evt) => {\n      if (!menu.contains(evt.target)) {\n        menu.remove();\n        document.removeEventListener('click', onDocClick);\n      }\n    };\n    setTimeout(() => document.addEventListener('click', onDocClick), 50);\n  }\n\n  generateDiagramThumbnailSvg(diag, diagNodes, hazardText, topEventText) {\n    const safeId = String((diag && diag.id) || 'default').replace(/[^a-zA-Z0-9_-]/g, '_');\n    const gridPatternId = `dotGrid-${safeId}`;\n    const shadowFilterId = `cardShadow-${safeId}`;\n\n    // 1. Prepare diagram nodes (or fallback minimal Hazard + Top Event pair if diagram has 0 nodes)\n    let nodes = Array.isArray(diagNodes) && diagNodes.length > 0 ? diagNodes : [\n      {\n        id: `fallback-haz-${safeId}`,\n        diagram_id: diag ? diag.id : 'default',\n        parent_id: null,\n        node_type: 'hazard',\n        title: 'HAZARD',\n        description: hazardText || 'Hazard',\n        x: 670,\n        y: 240,\n        width: 150,\n        height: 90,\n      },\n      {\n        id: `fallback-top-${safeId}`,\n        diagram_id: diag ? diag.id : 'default',\n        parent_id: `fallback-haz-${safeId}`,\n        node_type: 'top_event',\n        title: 'TOP EVENT',\n        description: topEventText || 'Top Event',\n        x: 680,\n        y: 380,\n        width: 130,\n        height: 130,\n      },\n    ];\n\n    const getNodeSize = (n) => {\n      const h = (n.node_type === 'top_event') ? 130 : ((n.node_type === 'hazard') ? 90 : 80);\n      const defaultW = (n.node_type === 'top_event') ? 130\n        : (n.node_type === 'hazard') ? 150\n        : (n.node_type === 'threat') ? 140\n        : (n.node_type === 'escalation_factor') ? 130\n        : (n.node_type === 'mitigation') ? 120\n        : 135;\n      const w = (Number.isFinite(n.width) && n.width > 40) ? n.width : defaultW;\n      return { w, h };\n    };\n\n    const layout = (this.app && this.app.layout) ? this.app.layout : null;\n    const layoutTargets = (layout && diag && diag.id && typeof layout.computeBowtieLayoutTargets === 'function')\n      ? layout.computeBowtieLayoutTargets(diag.id)\n      : new Map();\n\n    // Detect if raw coordinates are missing, overlapping, or unaligned compared to canonical layout\n    let useLayoutTargets = false;\n    for (let i = 0; i < nodes.length; i++) {\n      const a = nodes[i];\n      if (!Number.isFinite(a.x) || !Number.isFinite(a.y)) {\n        useLayoutTargets = true;\n        break;\n      }\n      const sa = getNodeSize(a);\n      for (let j = i + 1; j < nodes.length; j++) {\n        const b = nodes[j];\n        if (!Number.isFinite(b.x) || !Number.isFinite(b.y)) {\n          useLayoutTargets = true;\n          break;\n        }\n        const sb = getNodeSize(b);\n        if (\n          a.x < b.x + sb.w - 6 &&\n          a.x + sa.w - 6 > b.x &&\n          a.y < b.y + sb.h - 6 &&\n          a.y + sa.h - 6 > b.y\n        ) {\n          useLayoutTargets = true;\n          break;\n        }\n      }\n      if (useLayoutTargets) break;\n    }\n\n    if (!useLayoutTargets && layoutTargets.size === nodes.length && nodes.length > 2) {\n      let rMinX = Infinity, rMaxX = -Infinity, rMinY = Infinity, rMaxY = -Infinity;\n      let tMinX = Infinity, tMaxX = -Infinity, tMinY = Infinity, tMaxY = -Infinity;\n      nodes.forEach(n => {\n        const { w, h } = getNodeSize(n);\n        rMinX = Math.min(rMinX, n.x); rMaxX = Math.max(rMaxX, n.x + w);\n        rMinY = Math.min(rMinY, n.y); rMaxY = Math.max(rMaxY, n.y + h);\n        const t = layoutTargets.get(n.id);\n        if (t) {\n          tMinX = Math.min(tMinX, t.tx); tMaxX = Math.max(tMaxX, t.tx + w);\n          tMinY = Math.min(tMinY, t.ty); tMaxY = Math.max(tMaxY, t.ty + h);\n        }\n      });\n      if ((rMaxY - rMinY) > (tMaxY - tMinY) * 1.35 || (rMaxX - rMinX) > (tMaxX - tMinX) * 1.35) {\n        useLayoutTargets = true;\n      }\n    }\n\n    const posMap = new Map();\n    nodes.forEach((n, idx) => {\n      const { w, h } = getNodeSize(n);\n      const t = layoutTargets.get(n.id);\n      const x = Math.round((useLayoutTargets && t) ? t.tx : (Number.isFinite(n.x) ? n.x : (t ? t.tx : 680)));\n      const y = Math.round((useLayoutTargets && t) ? t.ty : (Number.isFinite(n.y) ? n.y : (t ? t.ty : 380 + idx * 110)));\n      posMap.set(n.id, { x, y, w, h, node: n });\n    });\n\n    // 2. Build canonical orthogonal trunk-and-branch connection wires (identical to full-screen BowtieCanvas.renderConnections)\n    const wirePaths = [];\n    const addPath = (d) => {\n      if (d) {\n        wirePaths.push(`<path d=\"${d}\" fill=\"none\" stroke=\"#94a3b8\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>`);\n      }\n    };\n\n    const connectOrthogonal = (sX, sY, tX, tY) => {\n      if (Math.abs(sY - tY) < 6) {\n        const avgY = Math.round((sY + tY) / 2);\n        return `M ${Math.round(sX)} ${avgY} L ${Math.round(tX)} ${avgY}`;\n      }\n      const midX = Math.round((sX + tX) / 2);\n      return `M ${Math.round(sX)} ${Math.round(sY)} L ${midX} ${Math.round(sY)} L ${midX} ${Math.round(tY)} L ${Math.round(tX)} ${Math.round(tY)}`;\n    };\n\n    const topEvents = nodes\n      .filter(n => n.node_type === 'top_event')\n      .sort((a, b) => (posMap.get(a.id).y) - (posMap.get(b.id).y));\n    const handledNodeIds = new Set();\n    nodes.forEach(n => {\n      if (n.node_type === 'hazard' || n.node_type === 'top_event') {\n        handledNodeIds.add(n.id);\n      }\n    });\n\n    // 2a. Hazard -> Top Event vertical wires\n    topEvents.forEach(te => {\n      const tePos = posMap.get(te.id);\n      let hNode = te.parent_id ? nodes.find(n => n.id === te.parent_id) : null;\n      if (!hNode) {\n        const hazardsAbove = nodes\n          .filter(n => n.node_type === 'hazard' && posMap.get(n.id).y < tePos.y)\n          .sort((a, b) => {\n            const pa = posMap.get(a.id);\n            const pb = posMap.get(b.id);\n            return Math.abs(pa.x - tePos.x) - Math.abs(pb.x - tePos.x) || pb.y - pa.y;\n          });\n        hNode = hazardsAbove[0] || nodes.find(n => n.node_type === 'hazard');\n      }\n      if (hNode) {\n        const hPos = posMap.get(hNode.id);\n        const sX = Math.round(hPos.x + hPos.w / 2);\n        const sY = Math.round(hPos.y + hPos.h);\n        const tX = Math.round(tePos.x + tePos.w / 2);\n        const tY = Math.round(tePos.y);\n        addPath(`M ${sX} ${sY} L ${tX} ${tY}`);\n        handledNodeIds.add(te.id);\n        handledNodeIds.add(hNode.id);\n      }\n    });\n\n    // Helper: Escalation Factor & Mitigation trees hanging under a barrier\n    const renderBarrierEscalationTree = (barrier, tePos = null) => {\n      handledNodeIds.add(barrier.id);\n      const bPos = posMap.get(barrier.id);\n      if (!bPos) return;\n      const efs = nodes\n        .filter(n => n.parent_id === barrier.id && n.node_type === 'escalation_factor')\n        .sort((a, b) => posMap.get(a.id).y - posMap.get(b.id).y);\n      if (efs.length === 0) return;\n\n      const bCenterX = Math.round(bPos.x + bPos.w / 2);\n      const bBottom = Math.round(bPos.y + bPos.h);\n\n      efs.forEach((ef, j) => {\n        handledNodeIds.add(ef.id);\n        const efPos = posMap.get(ef.id);\n        const efCenterX = Math.round(efPos.x + efPos.w / 2);\n        const efTop = Math.round(efPos.y);\n\n        if (j === 0) {\n          if (Math.abs(efCenterX - bCenterX) < 5) {\n            addPath(`M ${bCenterX} ${bBottom} L ${bCenterX} ${efTop}`);\n          } else {\n            const midY = Math.round((bBottom + efTop) / 2);\n            addPath(`M ${bCenterX} ${bBottom} L ${bCenterX} ${midY} L ${efCenterX} ${midY} L ${efCenterX} ${efTop}`);\n          }\n        } else {\n          const prevEfPos = posMap.get(efs[j - 1].id);\n          const prevBottom = Math.round(prevEfPos.y + prevEfPos.h);\n          const prevCenterX = Math.round(prevEfPos.x + prevEfPos.w / 2);\n          if (Math.abs(efCenterX - prevCenterX) < 5) {\n            addPath(`M ${prevCenterX} ${prevBottom} L ${prevCenterX} ${efTop}`);\n          } else {\n            const midY = Math.round((prevBottom + efTop) / 2);\n            addPath(`M ${prevCenterX} ${prevBottom} L ${prevCenterX} ${midY} L ${efCenterX} ${midY} L ${efCenterX} ${efTop}`);\n          }\n        }\n\n        const mits = nodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n        if (mits.length > 0) {\n          const isThreatSide = barrier.node_type === 'threat_barrier' || (tePos ? bPos.x < tePos.x : true);\n          const mitYs = mits.map(n => posMap.get(n.id).y);\n          const mitXs = mits.map(n => posMap.get(n.id).x);\n          const ySpread = Math.max(...mitYs) - Math.min(...mitYs);\n          const xSpread = Math.max(...mitXs) - Math.min(...mitXs);\n          const isVertical = ySpread > 20 || (ySpread >= xSpread && mits.length > 1);\n\n          if (isVertical) {\n            mits.sort((a, b) => posMap.get(a.id).y - posMap.get(b.id).y);\n          } else if (isThreatSide) {\n            mits.sort((a, b) => posMap.get(b.id).x - posMap.get(a.id).x);\n          } else {\n            mits.sort((a, b) => posMap.get(a.id).x - posMap.get(b.id).x);\n          }\n\n          mits.forEach((mit, m) => {\n            handledNodeIds.add(mit.id);\n            const mitPos = posMap.get(mit.id);\n            const efPinY = Math.round(efPos.y + 40);\n            const mitPinY = Math.round(mitPos.y + mitPos.h / 2);\n\n            if (isVertical) {\n              if (isThreatSide) {\n                addPath(connectOrthogonal(efPos.x, efPinY, mitPos.x + mitPos.w, mitPinY));\n              } else {\n                addPath(connectOrthogonal(efPos.x + efPos.w, efPinY, mitPos.x, mitPinY));\n              }\n            } else if (isThreatSide) {\n              if (m === 0) {\n                addPath(connectOrthogonal(efPos.x, efPinY, mitPos.x + mitPos.w, mitPinY));\n              } else {\n                const prevMitPos = posMap.get(mits[m - 1].id);\n                addPath(connectOrthogonal(prevMitPos.x, Math.round(prevMitPos.y + 40), mitPos.x + mitPos.w, mitPinY));\n              }\n            } else {\n              if (m === 0) {\n                addPath(connectOrthogonal(efPos.x + efPos.w, efPinY, mitPos.x, mitPinY));\n              } else {\n                const prevMitPos = posMap.get(mits[m - 1].id);\n                addPath(connectOrthogonal(prevMitPos.x + prevMitPos.w, Math.round(prevMitPos.y + 40), mitPos.x, mitPinY));\n              }\n            }\n          });\n        }\n      });\n    };\n\n    const ownership = (layout && topEvents.length > 0 && typeof layout.buildClusterOwnership === 'function')\n      ? layout.buildClusterOwnership(nodes, topEvents)\n      : new Map();\n\n    // 2b. Left & Right Wings for each Top Event cluster\n    topEvents.forEach(topEvent => {\n      const tePos = posMap.get(topEvent.id);\n      const teCenterY = Math.round(tePos.y + tePos.h / 2);\n      const leftTrunkX = Math.round(tePos.x - 55);\n      const rightTrunkX = Math.round(tePos.x + tePos.w + 55);\n\n      // Left Wing: Threats & Threat Barriers\n      const threats = (layout && typeof layout.getClusterThreats === 'function')\n        ? layout.getClusterThreats(topEvent, nodes, ownership)\n        : nodes.filter(n => n.node_type === 'threat').sort((a, b) => posMap.get(a.id).y - posMap.get(b.id).y);\n\n      const leftBranches = threats.map(threat => {\n        handledNodeIds.add(threat.id);\n        const barriers = (layout && typeof layout.getLinearChainForThreat === 'function')\n          ? layout.getLinearChainForThreat(threat, nodes)\n          : nodes.filter(n => n.node_type === 'threat_barrier' && n.parent_id === threat.id);\n        barriers.forEach(b => handledNodeIds.add(b.id));\n        return { threat, barriers };\n      });\n\n      if (leftBranches.length > 0) {\n        const branchYs = leftBranches.map(b => {\n          const nearest = (b.barriers.length > 0) ? b.barriers[b.barriers.length - 1] : b.threat;\n          return Math.round(posMap.get(nearest.id).y + 40);\n        });\n        const minLeftY = Math.min(teCenterY, ...branchYs);\n        const maxLeftY = Math.max(teCenterY, ...branchYs);\n\n        addPath(`M ${tePos.x} ${teCenterY} L ${leftTrunkX} ${teCenterY}`);\n        addPath(`M ${leftTrunkX} ${minLeftY} L ${leftTrunkX} ${maxLeftY}`);\n\n        leftBranches.forEach(branch => {\n          const tPos = posMap.get(branch.threat.id);\n          const bList = branch.barriers;\n\n          if (bList.length === 0) {\n            const tY = Math.round(tPos.y + 40);\n            const tRight = Math.round(tPos.x + tPos.w);\n            addPath(`M ${tRight} ${tY} L ${leftTrunkX} ${tY} L ${leftTrunkX} ${teCenterY} L ${tePos.x} ${teCenterY}`);\n          } else {\n            const lastBPos = posMap.get(bList[bList.length - 1].id);\n            const lastBY = Math.round(lastBPos.y + 40);\n            const lastBRight = Math.round(lastBPos.x + lastBPos.w);\n            addPath(`M ${lastBRight} ${lastBY} L ${leftTrunkX} ${lastBY} L ${leftTrunkX} ${teCenterY} L ${tePos.x} ${teCenterY}`);\n\n            for (let i = bList.length - 1; i > 0; i--) {\n              const rPos = posMap.get(bList[i].id);\n              const lPos = posMap.get(bList[i - 1].id);\n              addPath(connectOrthogonal(lPos.x + lPos.w, lPos.y + 40, rPos.x, rPos.y + 40));\n            }\n\n            const firstBPos = posMap.get(bList[0].id);\n            addPath(connectOrthogonal(tPos.x + tPos.w, tPos.y + 40, firstBPos.x, firstBPos.y + 40));\n          }\n\n          bList.forEach(barrier => renderBarrierEscalationTree(barrier, tePos));\n        });\n      }\n\n      // Right Wing: Outcome Barriers & Outcomes\n      const rightBranches = (layout && typeof layout.getClusterRightBranches === 'function')\n        ? layout.getClusterRightBranches(topEvent, nodes, ownership)\n        : nodes\n            .filter(n => n.node_type === 'outcome')\n            .sort((a, b) => posMap.get(a.id).y - posMap.get(b.id).y)\n            .map(oc => ({ chain: [oc] }));\n\n      if (rightBranches.length > 0) {\n        const branchYs = rightBranches\n          .filter(b => b.chain && b.chain.length > 0)\n          .map(b => Math.round(posMap.get(b.chain[0].id).y + 40));\n        if (branchYs.length > 0) {\n          const minRightY = Math.min(teCenterY, ...branchYs);\n          const maxRightY = Math.max(teCenterY, ...branchYs);\n\n          addPath(`M ${tePos.x + tePos.w} ${teCenterY} L ${rightTrunkX} ${teCenterY}`);\n          addPath(`M ${rightTrunkX} ${minRightY} L ${rightTrunkX} ${maxRightY}`);\n\n          rightBranches.forEach(branch => {\n            const chain = branch.chain || [];\n            if (chain.length === 0) return;\n\n            const firstPos = posMap.get(chain[0].id);\n            handledNodeIds.add(chain[0].id);\n            const firstY = Math.round(firstPos.y + 40);\n            addPath(`M ${tePos.x + tePos.w} ${teCenterY} L ${rightTrunkX} ${teCenterY} L ${rightTrunkX} ${firstY} L ${firstPos.x} ${firstY}`);\n\n            for (let i = 0; i < chain.length - 1; i++) {\n              const fromPos = posMap.get(chain[i].id);\n              const toPos = posMap.get(chain[i + 1].id);\n              handledNodeIds.add(chain[i + 1].id);\n              addPath(connectOrthogonal(fromPos.x + fromPos.w, fromPos.y + 40, toPos.x, toPos.y + 40));\n            }\n\n            chain.forEach(item => renderBarrierEscalationTree(item, tePos));\n          });\n        }\n      }\n    });\n\n    // 2c. Fallback wires for any remaining parent-child pairs\n    nodes.forEach(node => {\n      if (!node.parent_id || handledNodeIds.has(node.id)) return;\n      if (node.node_type === 'hazard' || node.node_type === 'top_event') return;\n      const parentPos = posMap.get(node.parent_id);\n      const nodePos = posMap.get(node.id);\n      if (parentPos && nodePos && parentPos.node.node_type !== 'hazard') {\n        const isLeft = nodePos.x < parentPos.x;\n        const sX = isLeft ? parentPos.x : (parentPos.x + parentPos.w);\n        const sY = parentPos.y + 40;\n        const tX = isLeft ? (nodePos.x + nodePos.w) : nodePos.x;\n        const tY = nodePos.y + 40;\n        addPath(connectOrthogonal(sX, sY, tX, tY));\n        handledNodeIds.add(node.id);\n      }\n    });\n\n    // 3. Render realistic full-screen node cards (.bowtie-node fidelity)\n    const customColors = (this.app && typeof this.app.getCustomColors === 'function') ? this.app.getCustomColors() : {};\n    const customNames = (this.app && typeof this.app.getCustomNames === 'function') ? this.app.getCustomNames() : {};\n\n    const truncateStr = (str, maxLen) => {\n      const s = String(str || '').trim();\n      return s.length > maxLen ? s.slice(0, Math.max(1, maxLen - 1)) + '…' : s;\n    };\n\n    const wrapTextLines = (text, maxCharsPerLine, maxLines) => {\n      const cleaned = String(text || '').replace(/\\s+/g, ' ').trim();\n      if (!cleaned) return [];\n      const words = cleaned.split(' ');\n      const lines = [];\n      let current = '';\n      for (let i = 0; i < words.length; i++) {\n        const word = words[i];\n        const candidate = current ? `${current} ${word}` : word;\n        if (candidate.length <= maxCharsPerLine) {\n          current = candidate;\n        } else if (current) {\n          lines.push(current);\n          if (lines.length >= maxLines) {\n            const last = lines[maxLines - 1];\n            lines[maxLines - 1] = (last.length >= maxCharsPerLine ? last.slice(0, Math.max(1, maxCharsPerLine - 1)) : last) + '…';\n            return lines;\n          }\n          current = word.length > maxCharsPerLine ? word.slice(0, maxCharsPerLine - 1) + '…' : word;\n        } else {\n          lines.push(word.slice(0, Math.max(1, maxCharsPerLine - 1)) + '…');\n          if (lines.length >= maxLines) return lines;\n          current = '';\n        }\n      }\n      if (current && lines.length < maxLines) {\n        lines.push(current);\n      } else if (current && lines.length >= maxLines) {\n        const last = lines[maxLines - 1];\n        if (!last.endsWith('…')) {\n          lines[maxLines - 1] = (last.length >= maxCharsPerLine ? last.slice(0, Math.max(1, maxCharsPerLine - 1)) : last) + '…';\n        }\n      }\n      return lines;\n    };\n\n    const clipDefs = [];\n    const nodeCardSvgs = [];\n    nodes.forEach((node, nodeIdx) => {\n      const pos = posMap.get(node.id);\n      if (!pos) return;\n      const { x, y, w, h } = pos;\n      const th = node.node_type === 'threat' ? node : null;\n      const oc = node.node_type === 'outcome' ? node : null;\n      const thTitle = th ? (this.app ? this.app.getNodeDisplayTitle(th) : (th.title || 'Threat')) : 'Threat';\n      const ocTitle = oc ? (this.app ? this.app.getNodeDisplayTitle(oc) : (oc.title || 'Outcome')) : 'Outcome';\n      const displayTitle = th\n        ? thTitle\n        : (oc\n            ? ocTitle\n            : ((this.app && typeof this.app.getNodeDisplayTitle === 'function')\n                ? this.app.getNodeDisplayTitle(node)\n                : (customNames[node.node_type] || node.title || node.node_type)));\n\n      if (node.node_type === 'hazard') {\n        const clipHazId = `clipHaz-${safeId}-${nodeIdx}`;\n        clipDefs.push(`<clipPath id=\"${clipHazId}\"><rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" rx=\"6\"/></clipPath>`);\n        const borderColor = customColors['hazard'] || '#eab308';\n        const hdrLabel = truncateStr((displayTitle || 'HAZARD').toUpperCase(), 18);\n        const rawDesc = (node.description || '').trim() || hazardText || 'Hazard';\n        const bodyLines = wrapTextLines(rawDesc, 20, 3);\n        const bodySvg = bodyLines.map((line, idx) =>\n          `<text x=\"${x + w / 2}\" y=\"${y + 41 + idx * 13.5}\" fill=\"#0f172a\" font-size=\"10.5\" font-weight=\"500\" text-anchor=\"middle\" font-family=\"Inter, system-ui, -apple-system, sans-serif\">${this.escapeHtml(line)}</text>`\n        ).join('');\n\n        nodeCardSvgs.push(`\n          <!-- HAZARD -->\n          <g class=\"thumb-node thumb-node-hazard\">\n            <rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" rx=\"6\" fill=\"#ffffff\" stroke=\"${borderColor}\" stroke-width=\"2.2\" filter=\"url(#${shadowFilterId})\"/>\n            <g clip-path=\"url(#${clipHazId})\">\n              <line x1=\"${x + 1}\" y1=\"${y + 22}\" x2=\"${x + w - 1}\" y2=\"${y + 22}\" stroke=\"#fef08a\" stroke-width=\"1.2\"/>\n            </g>\n            <text x=\"${x + w / 2}\" y=\"${y + 15}\" fill=\"#854d0e\" font-size=\"9.5\" font-weight=\"700\" text-anchor=\"middle\" font-family=\"Inter, system-ui, -apple-system, sans-serif\">${this.escapeHtml(hdrLabel)}</text>\n            ${bodySvg}\n          </g>\n        `);\n      } else if (node.node_type === 'top_event') {\n        const clipTopId = `clipTop-${safeId}-${nodeIdx}`;\n        clipDefs.push(`<clipPath id=\"${clipTopId}\"><rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" rx=\"12\"/></clipPath>`);\n        const borderColor = customColors['top_event'] || '#f97316';\n        const hdrLabel = truncateStr(displayTitle || 'TOP EVENT', 16);\n        const rawDesc = (node.description || '').trim() || topEventText || 'Top Event';\n        const bodyLines = wrapTextLines(rawDesc, 17, 4);\n        const bodySvg = bodyLines.map((line, idx) =>\n          `<text x=\"${x + w / 2}\" y=\"${y + 46 + idx * 14}\" fill=\"#0f172a\" font-size=\"10.5\" font-weight=\"500\" text-anchor=\"middle\" font-family=\"Inter, system-ui, -apple-system, sans-serif\">${this.escapeHtml(line)}</text>`\n        ).join('');\n\n        nodeCardSvgs.push(`\n          <!-- TOP EVENT -->\n          <g class=\"thumb-node thumb-node-top-event\">\n            <rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" rx=\"12\" fill=\"#ffffff\" stroke=\"${borderColor}\" stroke-width=\"3\" filter=\"url(#${shadowFilterId})\"/>\n            <g clip-path=\"url(#${clipTopId})\">\n              <text x=\"${x + w / 2}\" y=\"${y + 21}\" fill=\"#c2410c\" font-size=\"10.5\" font-weight=\"700\" text-anchor=\"middle\" font-family=\"Inter, system-ui, -apple-system, sans-serif\">${this.escapeHtml(hdrLabel)}</text>\n            </g>\n            <circle cx=\"${x}\" cy=\"${y + h / 2}\" r=\"4.5\" fill=\"#ffffff\" stroke=\"${borderColor}\" stroke-width=\"2\"/>\n            <circle cx=\"${x + w}\" cy=\"${y + h / 2}\" r=\"4.5\" fill=\"#ffffff\" stroke=\"${borderColor}\" stroke-width=\"2\"/>\n            ${bodySvg}\n          </g>\n        `);\n      } else {\n        let headerBg = '#334155';\n        let borderCol = '#cbd5e1';\n        let headerFg = '#ffffff';\n        let borderWidth = 1.8;\n        let hasPlusBtn = true;\n\n        if (node.node_type === 'threat') {\n          headerBg = customColors['threat'] || '#2563eb';\n          borderCol = customColors['threat'] || '#2563eb';\n          borderWidth = 2;\n        } else if (node.node_type === 'threat_barrier' || node.node_type === 'outcome_barrier') {\n          headerBg = customColors[node.node_type] || '#334155';\n          borderCol = customColors[node.node_type] || '#cbd5e1';\n          borderWidth = 1.6;\n        } else if (node.node_type === 'outcome') {\n          headerBg = customColors['outcome'] || '#dc2626';\n          borderCol = customColors['outcome'] || '#dc2626';\n          borderWidth = 2;\n        } else if (node.node_type === 'escalation_factor') {\n          headerBg = customColors['escalation_factor'] || '#eab308';\n          borderCol = customColors['escalation_factor'] || '#eab308';\n          headerFg = '#451a03';\n          borderWidth = 2;\n        } else if (node.node_type === 'mitigation') {\n          headerBg = customColors['mitigation'] || '#64748b';\n          borderCol = customColors['mitigation'] || '#94a3b8';\n          borderWidth = 1.6;\n          hasPlusBtn = false;\n        }\n\n        const hdrLabel = truncateStr(displayTitle, hasPlusBtn ? 14 : 17);\n        const rawDesc = (node.description || '').trim();\n        let bodySvg = '';\n        if (rawDesc) {\n          const bodyLines = wrapTextLines(rawDesc, 19, 3);\n          bodySvg = bodyLines.map((line, idx) =>\n            `<text x=\"${x + 7}\" y=\"${y + 37 + idx * 13}\" fill=\"#0f172a\" font-size=\"9.8\" font-weight=\"400\" font-family=\"Inter, system-ui, -apple-system, sans-serif\">${this.escapeHtml(line)}</text>`\n          ).join('');\n        } else {\n          // Subtle placeholder lines matching empty textarea on the full-screen canvas\n          bodySvg = `\n            <rect x=\"${x + 8}\" y=\"${y + 32}\" width=\"${Math.round(w * 0.68)}\" height=\"4.5\" rx=\"2.2\" fill=\"#e2e8f0\"/>\n            <rect x=\"${x + 8}\" y=\"${y + 42}\" width=\"${Math.round(w * 0.46)}\" height=\"4.5\" rx=\"2.2\" fill=\"#f1f5f9\"/>\n          `;\n        }\n\n        const headerPath = `M ${x} ${y + 22} L ${x} ${y + 6} A 6 6 0 0 1 ${x + 6} ${y} L ${x + w - 6} ${y} A 6 6 0 0 1 ${x + w} ${y + 6} L ${x + w} ${y + 22} Z`;\n        const plusBtnSvg = hasPlusBtn ? `\n          <rect x=\"${x + w - 19}\" y=\"${y + 4.5}\" width=\"13\" height=\"13\" rx=\"2.5\" fill=\"${node.node_type === 'escalation_factor' ? 'rgba(0,0,0,0.18)' : 'rgba(255,255,255,0.25)'}\"/>\n          <text x=\"${x + w - 12.5}\" y=\"${y + 14}\" fill=\"${headerFg}\" font-size=\"10\" font-weight=\"700\" text-anchor=\"middle\" font-family=\"Inter, system-ui, -apple-system, sans-serif\">+</text>\n        ` : '';\n\n        nodeCardSvgs.push(`\n          <g class=\"thumb-node thumb-node-${node.node_type}\">\n            <rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" rx=\"6\" fill=\"#ffffff\" stroke=\"${borderCol}\" stroke-width=\"${borderWidth}\" filter=\"url(#${shadowFilterId})\"/>\n            <path d=\"${headerPath}\" fill=\"${headerBg}\"/>\n            <text x=\"${x + 8}\" y=\"${y + 15}\" fill=\"${headerFg}\" font-size=\"10\" font-weight=\"700\" font-family=\"Inter, system-ui, -apple-system, sans-serif\">${this.escapeHtml(hdrLabel)}</text>\n            ${plusBtnSvg}\n            ${bodySvg}\n          </g>\n        `);\n      }\n    });\n\n    // 4. Compute full-screen camera viewBox (zoom-to-fit with landscape framing)\n    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n    posMap.forEach(({ x, y, w, h }) => {\n      minX = Math.min(minX, x);\n      minY = Math.min(minY, y);\n      maxX = Math.max(maxX, x + w);\n      maxY = Math.max(maxY, y + h);\n    });\n\n    const padX = 48;\n    const padY = 42;\n    const fullW = Math.max((maxX - minX) + padX * 2, 680);\n    const fullH = Math.max((maxY - minY) + padY * 2, 380);\n    const targetAspect = 280 / 155; // matches .diagram-card-preview (1.8065)\n\n    // For very tall diagrams (e.g. 25+ threats), cap the vertical camera window so the bowtie spans a realistic full-screen width while centered on the Top Event & Hazard\n    const maxAllowedH = Math.max(Math.round(fullW * 0.85), 880);\n    const effectiveH = Math.min(fullH, maxAllowedH);\n    const vbW = Math.round(Math.max(fullW, effectiveH * targetAspect));\n    const vbH = Math.round(vbW / targetAspect);\n\n    const centerX = (minX + maxX) / 2;\n    let centerY = (minY + maxY) / 2;\n    if (fullH > maxAllowedH && topEvents.length > 0) {\n      const primaryTePos = posMap.get(topEvents[0].id);\n      if (primaryTePos) {\n        const primaryHaz = nodes.find(n => n.node_type === 'hazard' && (n.id === topEvents[0].parent_id || posMap.get(n.id).y < primaryTePos.y));\n        const primaryHazPos = primaryHaz ? posMap.get(primaryHaz.id) : null;\n        centerY = primaryHazPos\n          ? (primaryHazPos.y + primaryTePos.y + primaryTePos.h) / 2\n          : (primaryTePos.y + primaryTePos.h / 2);\n      }\n    }\n\n    const vbX = Math.round(centerX - vbW / 2);\n    const vbY = Math.round(centerY - vbH / 2);\n\n    // Scale dot grid step with camera zoom so dots always look crisp like the full-screen canvas\n    const gridStep = Math.max(20, Math.round(vbW / 32));\n    const dotRadius = Math.max(1.2, Number((gridStep * 0.065).toFixed(2)));\n\n    return `\n      <svg class=\"diagram-thumbnail-svg\" viewBox=\"${vbX} ${vbY} ${vbW} ${vbH}\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMidYMid meet\">\n        <defs>\n          <pattern id=\"${gridPatternId}\" x=\"0\" y=\"0\" width=\"${gridStep}\" height=\"${gridStep}\" patternUnits=\"userSpaceOnUse\">\n            <circle cx=\"${Math.round(gridStep / 2)}\" cy=\"${Math.round(gridStep / 2)}\" r=\"${dotRadius}\" fill=\"#cbd5e1\" opacity=\"0.75\"/>\n          </pattern>\n          <filter id=\"${shadowFilterId}\" x=\"-8%\" y=\"-8%\" width=\"116%\" height=\"122%\">\n            <feDropShadow dx=\"0\" dy=\"2\" stdDeviation=\"2.5\" flood-color=\"#0f172a\" flood-opacity=\"0.08\"/>\n          </filter>\n          ${clipDefs.join('')}\n        </defs>\n        <rect x=\"${vbX}\" y=\"${vbY}\" width=\"${vbW}\" height=\"${vbH}\" fill=\"#f8fafc\"/>\n        <rect x=\"${vbX}\" y=\"${vbY}\" width=\"${vbW}\" height=\"${vbH}\" fill=\"url(#${gridPatternId})\"/>\n        <g class=\"thumb-wires-layer\">${wirePaths.join('')}</g>\n        <g class=\"thumb-nodes-layer\">${nodeCardSvgs.join('')}</g>\n      </svg>\n    `;\n  }\n\n  escapeHtml(text) {\n    if (!text) return '';\n    return text\n      .replace(/&/g, \"&amp;\")\n      .replace(/</g, \"&lt;\")\n      .replace(/>/g, \"&gt;\")\n      .replace(/\"/g, \"&quot;\")\n      .replace(/'/g, \"&#039;\");\n  }\n}\n",
    "js/export.js": "// Export Engine: Save File (.bowtie.json), PNG Image Exporter, and PDF Risk Assessment Report\nclass BowtieExporter {\n  constructor(app) {\n    this.app = app;\n  }\n\n  // 1. Download Workspace Save File (.bowtie.json)\n  downloadSaveFile() {\n    const ws = this.app.workspace;\n    const jsonStr = JSON.stringify(ws, null, 2);\n    const blob = new Blob([jsonStr], { type: 'application/json' });\n    const url = URL.createObjectURL(blob);\n\n    const a = document.createElement('a');\n    const title = (this.app.currentDiagram ? this.app.currentDiagram.title : 'bowtie_risk_workspace')\n      .replace(/[^a-zA-Z0-9_-]/g, '_')\n      .toLowerCase();\n    a.href = url;\n    a.download = `${title}_${new Date().toISOString().slice(0, 10)}.bowtie.json`;\n    document.body.appendChild(a);\n    a.click();\n    document.body.removeChild(a);\n    URL.revokeObjectURL(url);\n\n    this.app.showNotification('Workspace save file downloaded successfully!', 'success');\n  }\n\n  // 2. Import Workspace Save File (.bowtie.json)\n  handleImportFile(file) {\n    if (!file) return;\n    const reader = new FileReader();\n    reader.onload = async (e) => {\n      try {\n        const parsed = JSON.parse(e.target.result);\n        if (!parsed.diagrams || !parsed.nodes) {\n          throw new Error('Invalid Bowtie Risk Assessment workspace file.');\n        }\n\n        this.app.workspace = parsed;\n        if (parsed.diagrams.length > 0) {\n          this.app.currentDiagram = parsed.diagrams[0];\n        }\n\n        // Save to backend\n        await this.app.saveWorkspaceToBackend('Imported workspace from backup file');\n\n        // Refresh UI\n        this.app.initDiagramSelector();\n        this.app.canvas.render();\n        this.app.tree.render();\n        this.app.canvas.zoomFit();\n        this.app.showNotification('Workspace imported successfully!', 'success');\n      } catch (err) {\n        alert(`Failed to import file: ${err.message}`);\n      }\n    };\n    reader.readAsText(file);\n  }\n\n  // 3. Export Diagram as High-Resolution PNG Image\n  exportPng() {\n    const diag = this.app.currentDiagram;\n    const allNodes = this.app.workspace.nodes || [];\n    const nodes = diag ? allNodes.filter(n => n.diagram_id === diag.id) : allNodes;\n    if (!nodes || nodes.length === 0) {\n      alert('No diagram elements to export.');\n      return;\n    }\n\n    // Determine bounding box\n    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n    nodes.forEach(n => {\n      minX = Math.min(minX, n.x);\n      minY = Math.min(minY, n.y);\n      maxX = Math.max(maxX, n.x + (n.width || 140));\n      maxY = Math.max(maxY, n.y + (n.height || 85));\n    });\n\n    const padding = 60;\n    const titleHeaderHeight = 80;\n    const width = Math.max(maxX - minX + padding * 2, 800);\n    const height = Math.max(maxY - minY + padding * 2 + titleHeaderHeight, 600);\n\n    // Limit maximum dimension to 4096 to prevent Mobile Safari from running out of texture memory\n    const maxDimension = Math.max(width, height);\n    const scaleFactor = maxDimension > 2000 ? Math.max(1, 4096 / maxDimension) : 2;\n\n    const canvas = document.createElement('canvas');\n    canvas.width = Math.round(width * scaleFactor);\n    canvas.height = Math.round(height * scaleFactor);\n    const ctx = canvas.getContext('2d');\n    ctx.scale(scaleFactor, scaleFactor);\n\n    // Background\n    ctx.fillStyle = '#f8fafc';\n    ctx.fillRect(0, 0, width, height);\n\n    // Header bar (Dark Navy #0b1329)\n    ctx.fillStyle = '#0b1329';\n    ctx.fillRect(0, 0, width, 54);\n\n    // Header Title\n    ctx.fillStyle = '#ffffff';\n    ctx.font = 'bold 16px Inter, sans-serif';\n    ctx.fillText('Bowtie RISK Assessment', 24, 33);\n\n    const diagTitle = this.app.currentDiagram ? this.app.currentDiagram.title : 'Risk Assessment';\n    ctx.fillStyle = '#94a3b8';\n    ctx.font = '13px Inter, sans-serif';\n    ctx.fillText(`Diagram: ${diagTitle}  |  Exported: ${new Date().toLocaleDateString()}`, 240, 33);\n\n    // Offset coordinates\n    const offsetX = padding - minX;\n    const offsetY = padding + titleHeaderHeight - minY;\n\n    // Draw Connector Lines\n    const nodeMap = new Map();\n    nodes.forEach(n => nodeMap.set(n.id, n));\n\n    ctx.strokeStyle = '#94a3b8';\n    ctx.lineWidth = 2;\n\n    nodes.forEach(node => {\n      if (!node.parent_id) return;\n      const parent = nodeMap.get(node.parent_id);\n      if (!parent) return;\n\n      const pW = parent.width || 140;\n      const pH = parent.height || 85;\n      const cW = node.width || 140;\n      const cH = node.height || 85;\n\n      ctx.beginPath();\n      if (parent.node_type === 'hazard' && node.node_type === 'top_event') {\n        ctx.moveTo(parent.x + pW / 2 + offsetX, parent.y + pH + offsetY);\n        ctx.lineTo(node.x + cW / 2 + offsetX, node.y + offsetY);\n      } else if (node.node_type === 'escalation_factor') {\n        const sx = parent.x + pW / 2 + offsetX;\n        const sy = parent.y + pH + offsetY;\n        const ex = node.x + cW + offsetX;\n        const ey = node.y + cH / 2 + offsetY;\n        ctx.moveTo(sx, sy);\n        ctx.lineTo(sx, ey);\n        ctx.lineTo(ex, ey);\n      } else if (parent.node_type === 'top_event' && (node.node_type === 'threat' || node.node_type === 'threat_barrier')) {\n        const sx = parent.x + offsetX;\n        const sy = parent.y + pH / 2 + offsetY;\n        const ex = node.x + cW + offsetX;\n        const ey = node.y + cH / 2 + offsetY;\n        const mx = (sx + ex) / 2;\n        ctx.moveTo(sx, sy);\n        ctx.lineTo(mx, sy);\n        ctx.lineTo(mx, ey);\n        ctx.lineTo(ex, ey);\n      } else {\n        const sx = parent.x + pW + offsetX;\n        const sy = parent.y + pH / 2 + offsetY;\n        const ex = node.x + offsetX;\n        const ey = node.y + cH / 2 + offsetY;\n        const mx = (sx + ex) / 2;\n        ctx.moveTo(sx, sy);\n        ctx.lineTo(mx, sy);\n        ctx.lineTo(mx, ey);\n        ctx.lineTo(ex, ey);\n      }\n      ctx.stroke();\n    });\n\n    // Draw Nodes\n    const customColors = this.app.getCustomColors();\n    const customNames = this.app.getCustomNames();\n\n    nodes.forEach(node => {\n      const x = node.x + offsetX;\n      const y = node.y + offsetY;\n      const w = node.width || 140;\n      const h = node.height || 85;\n      const radius = node.node_type === 'top_event' ? 12 : 6;\n      const color = customColors[node.node_type] || '#334155';\n      const typeLabel = this.app ? this.app.getNodeDisplayTitle(node) : (customNames[node.node_type] || node.title);\n\n      // Card Background & Shadow\n      ctx.save();\n      ctx.shadowColor = 'rgba(0, 0, 0, 0.08)';\n      ctx.shadowBlur = 6;\n      ctx.shadowOffsetY = 2;\n      ctx.fillStyle = '#ffffff';\n      this.drawRoundedRect(ctx, x, y, w, h, radius);\n      ctx.fill();\n      ctx.restore();\n\n      // Border\n      ctx.strokeStyle = color;\n      ctx.lineWidth = node.node_type === 'top_event' ? 3 : (node.node_type === 'hazard' ? 2 : 1.5);\n      this.drawRoundedRect(ctx, x, y, w, h, radius);\n      ctx.stroke();\n\n      // Header Fill for Threats, Barriers, Outcomes, Escalations\n      if (node.node_type !== 'hazard' && node.node_type !== 'top_event') {\n        ctx.fillStyle = color;\n        this.drawTopRoundedRect(ctx, x, y, w, 24, radius);\n        ctx.fill();\n\n        // Header Title\n        ctx.fillStyle = '#ffffff';\n        ctx.font = 'bold 11px Inter, sans-serif';\n        ctx.fillText(typeLabel, x + 8, y + 16);\n      } else if (node.node_type === 'hazard') {\n        // Hazard Header Text\n        ctx.fillStyle = '#854d0e';\n        ctx.font = 'bold 10px Inter, sans-serif';\n        const tw = ctx.measureText(typeLabel.toUpperCase()).width;\n        ctx.fillText(typeLabel.toUpperCase(), x + (w - tw) / 2, y + 16);\n        ctx.strokeStyle = '#fef08a';\n        ctx.lineWidth = 1;\n        ctx.beginPath();\n        ctx.moveTo(x, y + 22);\n        ctx.lineTo(x + w, y + 22);\n        ctx.stroke();\n      } else if (node.node_type === 'top_event') {\n        ctx.fillStyle = '#c2410c';\n        ctx.font = 'bold 11px Inter, sans-serif';\n        const tw = ctx.measureText(typeLabel).width;\n        ctx.fillText(typeLabel, x + (w - tw) / 2, y + 22);\n      }\n\n      // Body Description Text (Wrapped)\n      ctx.fillStyle = '#0f172a';\n      ctx.font = '11px Inter, sans-serif';\n      const isCentered = (node.node_type === 'hazard' || node.node_type === 'top_event');\n      const textStartY = isCentered ? y + 38 : y + 42;\n      this.drawWrappedText(ctx, node.description || '', x + 8, textStartY, w - 16, 14, isCentered ? 'center' : 'left');\n    });\n\n    const filename = `${diagTitle.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase()}_bowtie.png`;\n    const dataUrl = canvas.toDataURL('image/png');\n\n    // Create Blob synchronously to preserve active mobile user gesture\n    let blob = null;\n    try {\n      const arr = dataUrl.split(',');\n      const mime = arr[0].match(/:(.*?);/)[1];\n      const bstr = atob(arr[1]);\n      let n = bstr.length;\n      const u8arr = new Uint8Array(n);\n      while (n--) {\n        u8arr[n] = bstr.charCodeAt(n);\n      }\n      blob = new Blob([u8arr], { type: mime });\n    } catch (err) {\n      console.warn('Sync blob creation error:', err);\n    }\n\n    const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ||\n                     (navigator.maxTouchPoints > 1 && window.innerWidth < 1024);\n\n    // 1. Mobile Web Share API with File (iOS 15+, Android Chrome)\n    if (isMobile && blob && navigator.canShare) {\n      try {\n        const file = new File([blob], filename, { type: 'image/png' });\n        if (navigator.canShare({ files: [file] })) {\n          navigator.share({\n            files: [file],\n            title: `${diagTitle} - Bowtie Risk Diagram`,\n            text: `Bowtie Risk Assessment Diagram: ${diagTitle}`\n          }).then(() => {\n            this.app.showNotification('Diagram image shared/saved successfully!', 'success');\n          }).catch((err) => {\n            if (err && err.name === 'AbortError') return; // User closed share sheet\n            console.warn('navigator.share failed, opening preview modal:', err);\n            this.showMobileImageModal(dataUrl, blob, filename);\n          });\n          return;\n        }\n      } catch (err) {\n        console.warn('Web share check failed:', err);\n      }\n    }\n\n    // 2. Mobile without file sharing or where sharing failed: show mobile preview modal\n    if (isMobile) {\n      this.showMobileImageModal(dataUrl, blob, filename);\n      return;\n    }\n\n    // 3. Desktop: Direct file download\n    const url = blob ? URL.createObjectURL(blob) : dataUrl;\n    const a = document.createElement('a');\n    a.href = url;\n    a.download = filename;\n    document.body.appendChild(a);\n    a.click();\n    document.body.removeChild(a);\n    if (blob) {\n      setTimeout(() => URL.revokeObjectURL(url), 60000);\n    }\n\n    this.app.showNotification('Diagram exported as high-resolution PNG image!', 'success');\n  }\n\n  // Mobile Image Export Modal Preview & Direct Saving\n  showMobileImageModal(dataUrl, blob, filename) {\n    const modal = document.getElementById('modalExportPng');\n    const previewImg = document.getElementById('exportPngPreviewImg');\n    const downloadLink = document.getElementById('btnExportPngDownloadLink');\n\n    if (!modal) {\n      const a = document.createElement('a');\n      a.href = dataUrl;\n      a.download = filename;\n      document.body.appendChild(a);\n      a.click();\n      document.body.removeChild(a);\n      this.app.showNotification('Diagram exported as PNG image!', 'success');\n      return;\n    }\n\n    if (previewImg) {\n      previewImg.src = dataUrl;\n    }\n\n    if (downloadLink) {\n      const url = blob ? URL.createObjectURL(blob) : dataUrl;\n      downloadLink.href = url;\n      downloadLink.download = filename;\n    }\n\n    modal.classList.add('show');\n    this.app.showNotification('Diagram image ready! Press and hold to save to Photos.', 'info');\n  }\n\n  // 4. Export Formatted PDF Risk Assessment Report\n  exportPdf() {\n    const diag = this.app.currentDiagram || { title: 'Bowtie Risk Assessment', description: '' };\n    const nodes = (this.app.workspace && this.app.workspace.nodes) ? this.app.workspace.nodes.filter(n => n.diagram_id === diag.id) : [];\n    const threats = nodes.filter(n => n.node_type === 'threat');\n    const barriers = nodes.filter(n => n.node_type === 'threat_barrier' || n.node_type === 'outcome_barrier');\n    const outcomes = nodes.filter(n => n.node_type === 'outcome');\n\n    // Create printable report window\n    const printWindow = window.open('', '_blank');\n    if (!printWindow) {\n      alert('Pop-up blocked. Please allow pop-ups for this site to generate the PDF report.');\n      return;\n    }\n\n    const escape = (text) => (this.app && typeof this.app.escapeHtml === 'function') ? this.app.escapeHtml(text) : (text || '');\n\n    const threatsRows = threats.map((th, i) => {\n      const thTitle = this.app ? this.app.getNodeDisplayTitle(th) : (th.title || 'Threat');\n      const thDesc = th.description || thTitle;\n      return `\n      <tr>\n        <td><strong>#${i + 1}</strong></td>\n        <td><strong>${escape(thTitle)}</strong></td>\n        <td>${escape(thDesc)}</td>\n        <td>${escape(th.criticality || 'High')}</td>\n        <td style=\"font-size: 11px;\">${escape(th.notes || '-')}</td>\n      </tr>\n    `}).join('');\n\n    const barriersRows = barriers.map((b, i) => {\n      const bTitle = this.app ? this.app.getNodeDisplayTitle(b) : (b.title || 'Barrier');\n      const bDesc = b.description || bTitle;\n      const bTypeLabel = `${bTitle} (${b.node_type === 'threat_barrier' ? 'Pre-event' : 'Post-event'})`;\n      return `\n      <tr>\n        <td><strong>#${i + 1}</strong></td>\n        <td><strong>${escape(bDesc)}</strong></td>\n        <td>${escape(bTypeLabel)}</td>\n        <td>${escape(b.responsible_role || 'Unassigned')}</td>\n        <td>${escape(b.effectiveness || 'Medium')}</td>\n        <td>${escape(b.criticality || 'Standard')}</td>\n        <td style=\"font-size: 11px;\">${escape(b.notes || '-')}</td>\n      </tr>\n    `}).join('');\n\n    const outcomesRows = outcomes.map((oc, i) => {\n      const ocTitle = this.app ? this.app.getNodeDisplayTitle(oc) : (oc.title || 'Outcome');\n      const ocDesc = oc.description || ocTitle;\n      return `\n      <tr>\n        <td><strong>#${i + 1}</strong></td>\n        <td><strong>${escape(ocTitle)}</strong></td>\n        <td>${escape(ocDesc)}</td>\n        <td>${escape(oc.criticality || 'Critical')}</td>\n        <td style=\"font-size: 11px;\">${escape(oc.notes || '-')}</td>\n      </tr>\n    `}).join('');\n\n    printWindow.document.write(`\n      <!DOCTYPE html>\n      <html>\n      <head>\n        <title>Risk Assessment Report - ${escape(diag.title)}</title>\n        <style>\n          body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif; color: #0f172a; margin: 30px; line-height: 1.5; }\n          .header { border-bottom: 3px solid #0b1329; padding-bottom: 12px; margin-bottom: 24px; display: flex; justify-content: space-between; align-items: flex-end; }\n          h1 { margin: 0; font-size: 24px; color: #0b1329; }\n          .meta { font-size: 13px; color: #64748b; }\n          .section { margin-bottom: 28px; }\n          h2 { font-size: 16px; border-bottom: 1px solid #e2e8f0; padding-bottom: 6px; margin-bottom: 12px; color: #1e293b; }\n          table { width: 100%; border-collapse: collapse; font-size: 12px; margin-top: 8px; }\n          th, td { border: 1px solid #cbd5e1; padding: 8px 10px; text-align: left; }\n          th { background-color: #f1f5f9; font-weight: 600; color: #334155; }\n          .badge { padding: 2px 6px; border-radius: 4px; font-size: 10px; font-weight: 700; color: #fff; background: #3b82f6; }\n          @media print {\n            button { display: none; }\n            body { margin: 15mm; }\n          }\n        </style>\n      </head>\n      <body>\n        <div class=\"header\">\n          <div>\n            <h1>Bowtie Risk Assessment Report</h1>\n            <div class=\"meta\" style=\"margin-top: 4px;\">Assessment Title: <strong>${escape(diag.title)}</strong></div>\n          </div>\n          <div class=\"meta\" style=\"text-align: right;\">\n            <div>Date: ${new Date().toLocaleDateString()}</div>\n            <div>Owner: ${escape(diag.created_by || 'djb@bowtierisk.com')}</div>\n          </div>\n        </div>\n\n        <div class=\"section\">\n          <h2>1. Executive Summary & Assessment Scope</h2>\n          <p>${escape(diag.description || 'Comprehensive Bowtie Risk Assessment evaluating primary hazards, threats, safety-critical barriers, escalation factors, and potential outcome consequences.')}</p>\n        </div>\n\n        ${threats.length > 0 ? `\n        <div class=\"section\">\n          <h2>2. Threats & Initiating Events</h2>\n          <table>\n            <thead>\n              <tr>\n                <th>ID</th>\n                <th>Category</th>\n                <th>Description</th>\n                <th>Criticality</th>\n                <th>Notes</th>\n              </tr>\n            </thead>\n            <tbody>\n              ${threatsRows}\n            </tbody>\n          </table>\n        </div>` : ''}\n\n        <div class=\"section\">\n          <h2>3. Safety-Critical Barrier Register & Effectiveness</h2>\n          <table>\n            <thead>\n              <tr>\n                <th>ID</th>\n                <th>Barrier Description</th>\n                <th>Category / Type</th>\n                <th>Responsible Role</th>\n                <th>Effectiveness</th>\n                <th>Criticality</th>\n                <th>Maintenance & Operating Notes</th>\n              </tr>\n            </thead>\n            <tbody>\n              ${barriersRows.length > 0 ? barriersRows : '<tr><td colspan=\"7\">No barriers registered.</td></tr>'}\n            </tbody>\n          </table>\n        </div>\n\n        ${outcomes.length > 0 ? `\n        <div class=\"section\">\n          <h2>4. Consequences & Potential Loss Outcomes</h2>\n          <table>\n            <thead>\n              <tr>\n                <th>ID</th>\n                <th>Category</th>\n                <th>Description</th>\n                <th>Severity</th>\n                <th>Notes</th>\n              </tr>\n            </thead>\n            <tbody>\n              ${outcomesRows}\n            </tbody>\n          </table>\n        </div>` : ''}\n\n        <div style=\"margin-top: 40px; text-align: center;\">\n          <button onclick=\"window.print()\" style=\"padding: 10px 20px; font-size: 14px; background: #2563eb; color: #fff; border: none; border-radius: 6px; cursor: pointer;\">\n            Print or Save as PDF\n          </button>\n        </div>\n      </body>\n      </html>\n    `);\n    printWindow.document.close();\n    this.app.showNotification('Prepared formatted PDF Risk Assessment Report!', 'success');\n  }\n\n  drawRoundedRect(ctx, x, y, width, height, radius) {\n    ctx.beginPath();\n    ctx.moveTo(x + radius, y);\n    ctx.lineTo(x + width - radius, y);\n    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);\n    ctx.lineTo(x + width, y + height - radius);\n    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);\n    ctx.lineTo(x + radius, y + height);\n    ctx.quadraticCurveTo(x, y + height, x, y + height - radius);\n    ctx.lineTo(x, y + radius);\n    ctx.quadraticCurveTo(x, y, x + radius, y);\n    ctx.closePath();\n  }\n\n  drawTopRoundedRect(ctx, x, y, width, height, radius) {\n    ctx.beginPath();\n    ctx.moveTo(x + radius, y);\n    ctx.lineTo(x + width - radius, y);\n    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);\n    ctx.lineTo(x + width, y + height);\n    ctx.lineTo(x, y + height);\n    ctx.lineTo(x, y + radius);\n    ctx.quadraticCurveTo(x, y, x + radius, y);\n    ctx.closePath();\n  }\n\n  drawWrappedText(ctx, text, x, y, maxWidth, lineHeight, align = 'left') {\n    const words = text.split(' ');\n    let line = '';\n    let currY = y;\n\n    for (let n = 0; n < words.length; n++) {\n      const testLine = line + words[n] + ' ';\n      const metrics = ctx.measureText(testLine);\n      const testWidth = metrics.width;\n      if (testWidth > maxWidth && n > 0) {\n        const trimmed = line.trim();\n        const drawX = align === 'center' ? x + (maxWidth - ctx.measureText(trimmed).width) / 2 : x;\n        ctx.fillText(trimmed, drawX, currY);\n        line = words[n] + ' ';\n        currY += lineHeight;\n        if (currY > y + lineHeight * 3) {\n          // Truncate if exceeding card height\n          const trunc = line.trim() + '...';\n          const drawTruncX = align === 'center' ? x + (maxWidth - ctx.measureText(trunc).width) / 2 : x;\n          ctx.fillText(trunc, drawTruncX, currY);\n          return;\n        }\n      } else {\n        line = testLine;\n      }\n    }\n    const finalTrimmed = line.trim();\n    const finalX = align === 'center' ? x + (maxWidth - ctx.measureText(finalTrimmed).width) / 2 : x;\n    ctx.fillText(finalTrimmed, finalX, currY);\n  }\n}\n",
    "js/inspector.js": "// Right-hand Pane: Active Element Inspector & Global vs Instance Sync Engine\nclass BowtieInspector {\n  constructor(app) {\n    this.app = app;\n    this.activeNode = null;\n    this.form = document.getElementById('inspectorForm');\n    this.emptyMsg = document.getElementById('emptySelectionMsg');\n\n    this.initFormEvents();\n  }\n\n  initFormEvents() {\n    if (!this.form) return;\n\n    // Field inputs with debounced update\n    const fields = [\n      { id: 'inspectTitle', prop: 'title' },\n      { id: 'inspectDesc', prop: 'description' },\n      { id: 'inspectResponsible', prop: 'responsible_role' },\n      { id: 'inspectEffectiveness', prop: 'effectiveness' },\n      { id: 'inspectCriticality', prop: 'criticality' },\n      { id: 'inspectNotes', prop: 'notes' },\n    ];\n\n    fields.forEach(({ id, prop }) => {\n      const el = document.getElementById(id);\n      if (el) {\n        el.addEventListener('focus', () => {\n          this._fieldPreEditSnapshot = this.app.captureWorkspaceSnapshot();\n        });\n        el.addEventListener('change', () => {\n          if (this._fieldPreEditSnapshot) {\n            this.app.pushSnapshotToUndo(this._fieldPreEditSnapshot, `Edit ${prop}`);\n            this._fieldPreEditSnapshot = null;\n          }\n        });\n        el.addEventListener('input', () => {\n          if (!this.activeNode) return;\n          if (this.app && !this.app.canEditCurrentDiagram()) return;\n\n          const oldVal = this.activeNode[prop];\n          const newVal = el.value;\n          this.activeNode[prop] = newVal;\n\n          // If linked to a global element and changing core attributes (title, description, role, effectiveness),\n          // check if user wants to propagate to all instances!\n          if (this.activeNode.global_element_id && (prop === 'description' || prop === 'title' || prop === 'responsible_role' || prop === 'effectiveness')) {\n            // Show prompt button or allow sync\n            const syncBtn = document.getElementById('btnSyncGlobal');\n            if (syncBtn) syncBtn.style.display = 'inline-flex';\n          }\n\n          // Update canvas node DOM text if description changed\n          if (prop === 'description') {\n            const canvasNodeEl = document.getElementById(`node-${this.activeNode.id}`);\n            if (canvasNodeEl) {\n              const ta = canvasNodeEl.querySelector('.node-textarea');\n              if (ta && ta.value !== newVal) ta.value = newVal;\n            }\n          }\n\n          // Update canvas node DOM header text if title changed\n          if (prop === 'title') {\n            this.activeNode.title_customized = newVal.trim() !== '';\n            const canvasNodeEl = document.getElementById(`node-${this.activeNode.id}`);\n            if (canvasNodeEl) {\n              const titleEl = canvasNodeEl.querySelector('.node-title-text');\n              if (titleEl) {\n                const isGlobal = !!this.activeNode.global_element_id;\n                const globalIndicator = isGlobal ? `<span title=\"Linked to Global Master Element\" style=\"font-size: 10px; margin-right: 4px; opacity: 0.85;\">🌐</span>` : '';\n                const attCount = this.activeNode.attachment_count || (this.app.workspace && this.app.workspace.attachments ? this.app.workspace.attachments.filter(a => a.node_id === this.activeNode.id).length : 0);\n                const attBadge = attCount > 0 ? `<span class=\"node-card-att-badge\" title=\"${attCount} attached document(s)\">📎 ${attCount}</span>` : '';\n                const displayTitle = this.app ? this.app.getNodeDisplayTitle(this.activeNode) : (newVal || '');\n                titleEl.innerHTML = `${globalIndicator}${this.app.escapeHtml(displayTitle)}${attBadge}`;\n              }\n            }\n            const badge = document.getElementById('activeNodeTypeBadge');\n            if (badge) {\n              badge.textContent = this.app ? this.app.getNodeDisplayTitle(this.activeNode) : (newVal || '');\n            }\n          }\n\n          this.app.tree.renderTree();\n          this.app.scheduleAutoSave(`Updated ${prop} for ${this.activeNode.title}`);\n        });\n      }\n    });\n\n    // Situation Runner Dynamic Timing Fields\n    const timingFields = [\n      { id: 'inspectTimeToBarrier', prop: 'time_to_barrier_sec' },\n      { id: 'inspectDelayToActivate', prop: 'delay_to_activate_sec' },\n      { id: 'inspectBarrierLife', prop: 'barrier_life_sec' },\n    ];\n\n    timingFields.forEach(({ id, prop }) => {\n      const el = document.getElementById(id);\n      if (el) {\n        el.addEventListener('focus', () => {\n          this._fieldPreEditSnapshot = this.app.captureWorkspaceSnapshot();\n        });\n        el.addEventListener('change', () => {\n          if (this._fieldPreEditSnapshot) {\n            this.app.pushSnapshotToUndo(this._fieldPreEditSnapshot, `Edit ${prop}`);\n            this._fieldPreEditSnapshot = null;\n          }\n        });\n        el.addEventListener('input', () => {\n          if (!this.activeNode) return;\n          if (this.app && !this.app.canEditCurrentDiagram()) return;\n          const val = el.value.trim();\n          this.activeNode[prop] = val === '' ? null : Math.max(0, parseFloat(val));\n          this.app.scheduleAutoSave(`Updated ${prop} for ${this.activeNode.title}`);\n        });\n      }\n    });\n\n    // Button: Center on Node\n    const btnCenter = document.getElementById('btnCenterOnNode');\n    if (btnCenter) {\n      const handleCenter = (e) => {\n        if (e && e.type === 'touchend') e.preventDefault();\n        if (this.activeNode) {\n          this.app.canvas.centerOnNode(this.activeNode.id);\n          if (window.innerWidth <= 768) {\n            const rightPane = document.getElementById('inspectorPane');\n            const backdrop = document.getElementById('mobileDrawerBackdrop');\n            if (rightPane) rightPane.classList.remove('drawer-open');\n            if (backdrop) backdrop.classList.remove('active');\n          }\n        }\n      };\n      btnCenter.addEventListener('click', handleCenter);\n      btnCenter.addEventListener('touchend', handleCenter, { passive: false });\n    }\n\n    // Button: Order Barriers by Time\n    const handleOrderBarriers = (e) => {\n      if (e && e.type === 'touchend') e.preventDefault();\n      if (this.activeNode) {\n        this.app.orderBarriersByTime(this.activeNode);\n      }\n    };\n    const btnOrderBarriers = document.getElementById('btnOrderBarriersByTime');\n    if (btnOrderBarriers) {\n      btnOrderBarriers.addEventListener('click', handleOrderBarriers);\n      btnOrderBarriers.addEventListener('touchend', handleOrderBarriers, { passive: false });\n    }\n    const btnInspectOrderBarriers = document.getElementById('btnInspectOrderBarriers');\n    if (btnInspectOrderBarriers) {\n      btnInspectOrderBarriers.addEventListener('click', handleOrderBarriers);\n      btnInspectOrderBarriers.addEventListener('touchend', handleOrderBarriers, { passive: false });\n    }\n\n    // Button: Sort Mitigations Vertically\n    const handleSortMitigations = (e) => {\n      if (e && e.type === 'touchend') e.preventDefault();\n      if (this.activeNode) {\n        this.app.sortMitigationsVertically(this.activeNode);\n      }\n    };\n    const btnInspectSortMit = document.getElementById('btnInspectSortMitigations');\n    if (btnInspectSortMit) {\n      btnInspectSortMit.addEventListener('click', handleSortMitigations);\n      btnInspectSortMit.addEventListener('touchend', handleSortMitigations, { passive: false });\n    }\n    const btnSortMitAction = document.getElementById('btnSortMitigationsVertically');\n    if (btnSortMitAction) {\n      btnSortMitAction.addEventListener('click', handleSortMitigations);\n      btnSortMitAction.addEventListener('touchend', handleSortMitigations, { passive: false });\n    }\n\n    // Button: Delete Node\n    const btnDelete = document.getElementById('btnDeleteActiveNode');\n    if (btnDelete) {\n      const handleDelete = (e) => {\n        if (e && e.type === 'touchend') e.preventDefault();\n        if (this.activeNode) {\n          const id = this.activeNode.id;\n          this.app.handleDeleteNode(id);\n          if (window.innerWidth <= 768) {\n            const rightPane = document.getElementById('inspectorPane');\n            const backdrop = document.getElementById('mobileDrawerBackdrop');\n            if (rightPane) rightPane.classList.remove('drawer-open');\n            if (backdrop) backdrop.classList.remove('active');\n          }\n        }\n      };\n      btnDelete.addEventListener('click', handleDelete);\n      btnDelete.addEventListener('touchend', handleDelete, { passive: false });\n    }\n\n    // Financial Costing Inputs\n    const costEstimateEl = document.getElementById('inspectCostEstimate');\n    if (costEstimateEl) {\n      costEstimateEl.addEventListener('focus', () => {\n        this._fieldPreEditSnapshot = this.app.captureWorkspaceSnapshot();\n      });\n      costEstimateEl.addEventListener('change', () => {\n        if (this._fieldPreEditSnapshot) {\n          this.app.pushSnapshotToUndo(this._fieldPreEditSnapshot, 'Edit Cost Estimate');\n          this._fieldPreEditSnapshot = null;\n        }\n      });\n      costEstimateEl.addEventListener('input', () => {\n        if (!this.activeNode) return;\n        const val = costEstimateEl.value.trim();\n        this.activeNode.cost_estimate = val === '' ? null : Math.max(0, parseFloat(val));\n        this.updateCostProtectionCard(this.activeNode);\n        this.app.scheduleAutoSave(`Updated cost estimate for ${this.activeNode.title}`);\n      });\n    }\n\n    const costCurrencyEl = document.getElementById('inspectCostCurrency');\n    if (costCurrencyEl) {\n      costCurrencyEl.addEventListener('focus', () => {\n        this._fieldPreEditSnapshot = this.app.captureWorkspaceSnapshot();\n      });\n      costCurrencyEl.addEventListener('change', () => {\n        if (!this.activeNode) return;\n        if (this._fieldPreEditSnapshot) {\n          this.app.pushSnapshotToUndo(this._fieldPreEditSnapshot, 'Edit Cost Currency');\n          this._fieldPreEditSnapshot = null;\n        }\n        this.activeNode.cost_currency = costCurrencyEl.value;\n        this.updateCostProtectionCard(this.activeNode);\n        this.app.scheduleAutoSave(`Updated cost currency for ${this.activeNode.title}`);\n      });\n    }\n\n    // Button: Link to Global Element\n    const btnLink = document.getElementById('btnLinkGlobal');\n    if (btnLink) {\n      btnLink.addEventListener('click', () => {\n        this.openLinkGlobalModal();\n      });\n    }\n\n    // Button: Sync Global to All Instances\n    const btnSync = document.getElementById('btnSyncGlobal');\n    if (btnSync) {\n      btnSync.addEventListener('click', () => {\n        this.triggerGlobalSyncConfirmation();\n      });\n    }\n\n    // Button: Manage Element Permissions\n    const btnPerm = document.getElementById('btnManageElemPerm');\n    if (btnPerm) {\n      btnPerm.addEventListener('click', () => {\n        this.app.openShareModalForElement(this.activeNode);\n      });\n    }\n\n    // Attachment upload events\n    const btnUpload = document.getElementById('btnUploadAttachment');\n    const inputUpload = document.getElementById('nodeAttachmentInput');\n    const dropZone = document.getElementById('attachmentDropZone');\n\n    if (btnUpload && inputUpload) {\n      btnUpload.addEventListener('click', () => inputUpload.click());\n      inputUpload.addEventListener('change', async (e) => {\n        if (!e.target.files) return;\n        const files = Array.from(e.target.files);\n        for (const file of files) {\n          await this.uploadAttachmentFile(file);\n        }\n        inputUpload.value = '';\n      });\n    }\n\n    if (dropZone) {\n      dropZone.addEventListener('dragover', (e) => {\n        e.preventDefault();\n        dropZone.classList.add('drag-over');\n      });\n      dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'));\n      dropZone.addEventListener('drop', async (e) => {\n        e.preventDefault();\n        dropZone.classList.remove('drag-over');\n        if (e.dataTransfer && e.dataTransfer.files) {\n          const files = Array.from(e.dataTransfer.files);\n          for (const file of files) {\n            await this.uploadAttachmentFile(file);\n          }\n        }\n      });\n    }\n  }\n\n  getNodeSuggestedDescription(node) {\n    if (this.app && this.app.canvas && typeof this.app.canvas.getNodeSuggestedDescription === 'function') {\n      return this.app.canvas.getNodeSuggestedDescription(node);\n    }\n    return 'Enter description or equipment tag...';\n  }\n\n  inspect(nodeId) {\n    const node = this.app.getNodeById(nodeId);\n    this.activeNode = node;\n\n    if (!node) {\n      if (this.emptyMsg) this.emptyMsg.style.display = 'flex';\n      if (this.form) this.form.style.display = 'none';\n      const btnOrderAction = document.getElementById('btnOrderBarriersByTime');\n      if (btnOrderAction) btnOrderAction.style.display = 'none';\n      const btnInspectOrderTiming = document.getElementById('btnInspectOrderBarriers');\n      if (btnInspectOrderTiming) btnInspectOrderTiming.style.display = 'none';\n      const btnInspectSortMit = document.getElementById('btnInspectSortMitigations');\n      if (btnInspectSortMit) btnInspectSortMit.style.display = 'none';\n      const btnSortMitAction = document.getElementById('btnSortMitigationsVertically');\n      if (btnSortMitAction) btnSortMitAction.style.display = 'none';\n      return;\n    }\n\n    if (this.emptyMsg) this.emptyMsg.style.display = 'none';\n    if (this.form) this.form.style.display = 'flex';\n\n    // Update Type Badge\n    const badge = document.getElementById('activeNodeTypeBadge');\n    const displayTitle = this.app ? this.app.getNodeDisplayTitle(node) : (node.title || '');\n    if (badge) {\n      const customColors = this.app.getCustomColors();\n      badge.textContent = displayTitle;\n      badge.style.backgroundColor = customColors[node.node_type] || '#334155';\n    }\n\n    // Populate Fields\n    const defaultLabels = {\n      hazard: 'Hazard',\n      top_event: 'Top event',\n      threat: 'Threat',\n      threat_barrier: 'Threat Barrier',\n      outcome_barrier: 'Outcome Barrier',\n      outcome: 'Outcome',\n      escalation_factor: 'Escalation Factor',\n      mitigation: 'Mitigation',\n    };\n    const isExplicitlyCustom = node.title_customized || (node.title && node.title.trim() && node.title.trim().toLowerCase() !== (defaultLabels[node.node_type] || '').toLowerCase());\n    const titleVal = isExplicitlyCustom ? node.title : displayTitle;\n    this.setFieldValue('inspectTitle', titleVal || '');\n    const titleEl = document.getElementById('inspectTitle');\n    if (titleEl) {\n      titleEl.placeholder = displayTitle;\n    }\n    this.setFieldValue('inspectDesc', node.description || '');\n    const descEl = document.getElementById('inspectDesc');\n    if (descEl) {\n      descEl.placeholder = this.getNodeSuggestedDescription(node);\n    }\n    this.setFieldValue('inspectResponsible', node.responsible_role || '');\n    this.setFieldValue('inspectEffectiveness', node.effectiveness || '');\n    this.setFieldValue('inspectCriticality', node.criticality || '');\n    this.setFieldValue('inspectNotes', node.notes || '');\n\n    // Situation Runner Timing Parameters\n    const simBox = document.getElementById('simulationTimingBox');\n    if (simBox) {\n      const isSimNode = ['threat_barrier', 'outcome_barrier', 'mitigation', 'escalation_factor', 'threat'].includes(node.node_type);\n      simBox.style.display = isSimNode ? 'block' : 'none';\n      if (isSimNode) {\n        this.setFieldValue('inspectTimeToBarrier', node.time_to_barrier_sec != null ? node.time_to_barrier_sec : '');\n        this.setFieldValue('inspectDelayToActivate', node.delay_to_activate_sec != null ? node.delay_to_activate_sec : '');\n        this.setFieldValue('inspectBarrierLife', node.barrier_life_sec != null ? node.barrier_life_sec : '');\n      }\n    }\n\n    // Financial Costing Parameters (Barriers & Mitigations)\n    const costBox = document.getElementById('financialCostingBox');\n    if (costBox) {\n      const isCostNode = ['threat_barrier', 'outcome_barrier', 'mitigation'].includes(node.node_type);\n      costBox.style.display = isCostNode ? 'block' : 'none';\n      if (isCostNode) {\n        this.setFieldValue('inspectCostCurrency', node.cost_currency || 'USD');\n        this.setFieldValue('inspectCostEstimate', node.cost_estimate != null ? node.cost_estimate : '');\n        this.updateCostProtectionCard(node);\n      }\n    }\n\n    // Toggle Order Barriers by Time buttons for threats, outcomes, barriers, and escalation factors\n    const isBranchNode = ['threat', 'outcome', 'threat_barrier', 'outcome_barrier', 'escalation_factor'].includes(node.node_type);\n    const btnOrderAction = document.getElementById('btnOrderBarriersByTime');\n    if (btnOrderAction) {\n      btnOrderAction.style.display = isBranchNode ? 'inline-flex' : 'none';\n    }\n    const btnOrderTiming = document.getElementById('btnInspectOrderBarriers');\n    if (btnOrderTiming) {\n      btnOrderTiming.style.display = isBranchNode ? 'flex' : 'none';\n    }\n\n    // Toggle Sort Mitigations Vertically buttons for mitigations and escalation factors\n    const isMitOrEf = ['mitigation', 'escalation_factor'].includes(node.node_type);\n    const btnInspectSortMit = document.getElementById('btnInspectSortMitigations');\n    if (btnInspectSortMit) {\n      btnInspectSortMit.style.display = isMitOrEf ? 'flex' : 'none';\n    }\n    const btnSortMitAction = document.getElementById('btnSortMitigationsVertically');\n    if (btnSortMitAction) {\n      btnSortMitAction.style.display = isMitOrEf ? 'inline-flex' : 'none';\n    }\n\n    // Master Element Status\n    this.updateGlobalLinkUI(node);\n\n    // Element Permissions\n    this.updatePermissionsUI(node);\n\n    // Supporting Document Attachments\n    this.loadAttachments(node.id);\n\n    // Read-only permission check for delete button\n    const canEdit = this.app ? this.app.canEditCurrentDiagram() : true;\n    const btnDelete = document.getElementById('btnDeleteActiveNode');\n    if (btnDelete) {\n      btnDelete.style.display = canEdit ? 'inline-flex' : 'none';\n    }\n  }\n\n  setFieldValue(id, val) {\n    const el = document.getElementById(id);\n    if (!el) return;\n    el.value = val;\n    const canEdit = this.app ? this.app.canEditCurrentDiagram() : true;\n    if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT') {\n      el.disabled = !canEdit;\n      if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {\n        el.readOnly = !canEdit;\n      }\n    }\n  }\n\n  render() {\n    if (this.activeNode) {\n      this.inspect(this.activeNode.id);\n    }\n  }\n\n  updateCostProtectionCard(node) {\n    if (!node) return;\n    const ratingEl = document.getElementById('inspectCostProtectionRating');\n    const badgeEl = document.getElementById('inspectCostEfficiencyBadge');\n    const textEl = document.getElementById('inspectCostSummaryText');\n    if (!ratingEl || !badgeEl || !textEl) return;\n\n    const eff = node.effectiveness || 'Medium';\n    const cost = node.cost_estimate;\n    const curr = node.cost_currency || 'USD';\n    const currSymbol = { USD: '$', EUR: '€', GBP: '£', AUD: 'A$', CAD: 'C$', NOK: 'kr', SGD: 'S$' }[curr] || '$';\n\n    ratingEl.textContent = eff + (node.criticality ? ` (${node.criticality})` : '');\n\n    if (cost == null || isNaN(cost)) {\n      badgeEl.textContent = 'Uncosted Element';\n      badgeEl.className = 'metric-value badge';\n      badgeEl.style.backgroundColor = '#f1f5f9';\n      badgeEl.style.color = '#64748b';\n      textEl.textContent = 'Add financial cost estimate to evaluate defensive ROI and cost-effectiveness.';\n    } else {\n      const formattedCost = `${currSymbol}${Number(cost).toLocaleString()}`;\n      if (eff.toLowerCase().includes('high') || eff.toLowerCase().includes('critical')) {\n        if (cost <= 40000) {\n          badgeEl.textContent = 'Optimal Protection / High ROI';\n          badgeEl.className = 'metric-value badge';\n          badgeEl.style.backgroundColor = '#dcfce7';\n          badgeEl.style.color = '#15803d';\n          textEl.textContent = `${formattedCost} investment provides high integrity protection against major hazard propagation.`;\n        } else {\n          badgeEl.textContent = 'Critical Capital Investment';\n          badgeEl.className = 'metric-value badge';\n          badgeEl.style.backgroundColor = '#dbeafe';\n          badgeEl.style.color = '#1d4ed8';\n          textEl.textContent = `${formattedCost} major asset defense system. Essential for regulatory SCE compliance.`;\n        }\n      } else if (eff.toLowerCase().includes('low')) {\n        if (cost > 15000) {\n          badgeEl.textContent = 'High Cost / Low Protection';\n          badgeEl.className = 'metric-value badge';\n          badgeEl.style.backgroundColor = '#fee2e2';\n          badgeEl.style.color = '#b91c1c';\n          textEl.textContent = `${formattedCost} for degraded barrier. Review maintenance or upgrade to higher integrity element.`;\n        } else {\n          badgeEl.textContent = 'Low Impact Control';\n          badgeEl.className = 'metric-value badge';\n          badgeEl.style.backgroundColor = '#fef3c7';\n          badgeEl.style.color = '#b45309';\n          textEl.textContent = `${formattedCost} low cost procedural or secondary defensive control.`;\n        }\n      } else {\n        // Medium\n        badgeEl.textContent = 'Standard Defensive Control';\n        badgeEl.className = 'metric-value badge';\n        badgeEl.style.backgroundColor = '#e0e7ff';\n        badgeEl.style.color = '#4338ca';\n        textEl.textContent = `${formattedCost} provides standard baseline risk reduction.`;\n      }\n    }\n  }\n\n  updateGlobalLinkUI(node) {\n    const badge = document.getElementById('globalLinkBadge');\n    const info = document.getElementById('globalLinkInfo');\n    const syncBtn = document.getElementById('btnSyncGlobal');\n    const linkBtn = document.getElementById('btnLinkGlobal');\n\n    if (!node.global_element_id) {\n      if (badge) {\n        badge.textContent = 'Standalone';\n        badge.className = 'status-pill';\n      }\n      if (info) info.textContent = 'This item is an independent instance. You can link it to a reusable master element.';\n      if (syncBtn) syncBtn.style.display = 'none';\n      if (linkBtn) linkBtn.textContent = 'Link to Master Element';\n    } else {\n      const ge = (this.app.workspace.global_elements || []).find(g => g.id === node.global_element_id);\n      const instances = this.app.workspace.nodes.filter(n => n.global_element_id === node.global_element_id);\n\n      if (badge) {\n        badge.textContent = `Master: ${ge ? ge.name : 'Linked'}`;\n        badge.className = 'status-pill linked';\n      }\n      if (info) {\n        info.innerHTML = `Linked to <strong>${this.escapeHtml(ge ? ge.name : 'Master Element')}</strong>. There are <strong>${instances.length}</strong> instances across diagrams.`;\n      }\n      if (syncBtn) syncBtn.style.display = 'inline-flex';\n      if (linkBtn) linkBtn.textContent = 'Change Master Link';\n    }\n  }\n\n  updatePermissionsUI(node) {\n    const list = document.getElementById('elementPermissionsList');\n    if (!list) return;\n\n    const perms = (this.app.workspace.permissions || []).filter(p => p.target_type === 'element' && p.target_id === (node.global_element_id || node.id));\n\n    if (perms.length === 0) {\n      list.innerHTML = `<span class=\"text-xs text-muted\">Inherits diagram permissions (All authorized users)</span>`;\n    } else {\n      list.innerHTML = perms.map(p => `\n        <div style=\"display:flex; justify-content:space-between; font-size:11px; padding:3px 0; border-bottom:1px solid #f1f5f9;\">\n          <span>${this.escapeHtml(p.user_email)}</span>\n          <span class=\"badge\" style=\"background:#0f172a;\">${this.escapeHtml((p.access_level || '').toUpperCase())}</span>\n        </div>\n      `).join('');\n    }\n  }\n\n  triggerGlobalSyncConfirmation() {\n    if (!this.activeNode || !this.activeNode.global_element_id) return;\n    const ge = (this.app.workspace.global_elements || []).find(g => g.id === this.activeNode.global_element_id);\n    if (!ge) return;\n\n    const instances = this.app.workspace.nodes.filter(n => n.global_element_id === ge.id);\n\n    const modal = document.getElementById('modalGlobalSyncPrompt');\n    const nameEl = document.getElementById('syncElementName');\n    const countEl = document.getElementById('syncInstancesCountText');\n\n    if (nameEl) nameEl.textContent = ge.name;\n    if (countEl) countEl.textContent = `This element appears in ${instances.length} locations across your diagrams.`;\n\n    if (modal) modal.classList.add('show');\n\n    // Wire choice buttons\n    const btnOnlyThis = document.getElementById('btnSyncOnlyThisInstance');\n    const btnAll = document.getElementById('btnSyncAllInstances');\n\n    const closeModal = () => modal.classList.remove('show');\n\n    btnOnlyThis.onclick = () => {\n      closeModal();\n      this.app.showNotification('Kept changes as local instance overrides.', 'info');\n      this.app.scheduleAutoSave(`Saved local override for instance ${this.activeNode.title}`);\n    };\n\n    btnAll.onclick = () => {\n      closeModal();\n      // Synchronize all instances!\n      ge.name = this.activeNode.description || ge.name;\n      const syncedTitle = (this.activeNode.title !== undefined && this.activeNode.title !== null) ? this.activeNode.title : '';\n      const isCustomized = !!this.activeNode.title_customized;\n      ge.category = syncedTitle;\n      ge.item_category = syncedTitle;\n      ge.responsible_role = this.activeNode.responsible_role || ge.responsible_role;\n      ge.effectiveness = this.activeNode.effectiveness || ge.effectiveness;\n      ge.criticality = this.activeNode.criticality || ge.criticality;\n      ge.updated_at = new Date().toISOString();\n\n      let syncedCount = 0;\n      this.app.workspace.nodes.forEach(n => {\n        if (n.global_element_id === ge.id) {\n          n.description = ge.name;\n          n.title = syncedTitle;\n          n.title_customized = isCustomized;\n          n.responsible_role = ge.responsible_role;\n          n.effectiveness = ge.effectiveness;\n          n.criticality = ge.criticality;\n          syncedCount++;\n        }\n      });\n\n      this.app.canvas.render();\n      this.app.tree.render();\n      this.app.showNotification(`Synchronized all ${syncedCount} instances of '${ge.name}' across diagrams!`, 'success');\n      this.app.scheduleAutoSave(`Synchronized ${syncedCount} instances of global element '${ge.name}'`);\n    };\n  }\n\n  openLinkGlobalModal() {\n    const globals = this.app.workspace.global_elements || [];\n    if (globals.length === 0) {\n      alert(\"No global master elements in library. Click 'Element Library' -> '+ New Master Element' in the left pane to create one.\");\n      return;\n    }\n\n    const options = globals.map(g => `${g.id}: ${g.name} (${g.node_type})`).join('\\n');\n    const chosen = prompt(`Select Master Element by ID to link:\\n\\n${options}`, globals[0].id);\n    if (!chosen) return;\n\n    const matched = globals.find(g => g.id.toLowerCase() === chosen.toLowerCase().trim() || g.name.toLowerCase().includes(chosen.toLowerCase().trim()));\n    if (matched) {\n      this.activeNode.global_element_id = matched.id;\n      this.activeNode.description = matched.name;\n      if (matched.category || matched.item_category) {\n        this.activeNode.title = matched.category || matched.item_category;\n        this.activeNode.title_customized = true;\n      }\n      this.activeNode.responsible_role = matched.responsible_role;\n      this.activeNode.effectiveness = matched.effectiveness;\n      this.activeNode.criticality = matched.criticality;\n\n      this.inspect(this.activeNode.id);\n      this.app.canvas.render();\n      this.app.tree.render();\n      this.app.scheduleAutoSave(`Linked element to master '${matched.name}'`);\n    }\n  }\n\n  async loadAttachments(nodeId) {\n    const listEl = document.getElementById('inspectorAttachmentsList');\n    const badgeEl = document.getElementById('inspectAttachmentCountBadge');\n    if (!listEl) return;\n\n    listEl.innerHTML = '<span class=\"text-xs text-muted\">Loading documents...</span>';\n\n    try {\n      const res = await this.app.apiFetch(`/api/elements/${encodeURIComponent(nodeId)}/attachments`);\n      if (!res.ok) throw new Error('Failed to load attachments');\n      const attachments = await res.json();\n\n      if (badgeEl) badgeEl.textContent = attachments.length;\n\n      if (this.activeNode && this.activeNode.id === nodeId) {\n        this.activeNode.attachment_count = attachments.length;\n      }\n\n      if (attachments.length === 0) {\n        listEl.innerHTML = '<span class=\"text-xs text-muted\">No documents attached yet. Click above or drag & drop files here.</span>';\n        return;\n      }\n\n      listEl.innerHTML = '';\n      attachments.forEach(att => {\n        const item = document.createElement('div');\n        item.className = 'attachment-item-card';\n\n        const iconHtml = this.getFileIconHtml(att.filename);\n        const formattedSize = this.formatFileSize(att.file_size);\n        const dateStr = att.uploaded_at ? new Date(att.uploaded_at).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) : '';\n\n        item.innerHTML = `\n          <div class=\"att-icon-wrap\">${iconHtml}</div>\n          <div class=\"att-info-wrap\">\n            <div class=\"att-filename\" title=\"${this.escapeHtml(att.filename)}\">${this.escapeHtml(att.filename)}</div>\n            <div class=\"att-meta-line\">\n              <span>${formattedSize}</span>\n              ${dateStr ? `<span>• ${dateStr}</span>` : ''}\n              ${att.uploaded_by ? `<span class=\"att-uploader\">• ${this.escapeHtml(att.uploaded_by.split('@')[0])}</span>` : ''}\n            </div>\n          </div>\n          <div class=\"att-actions-wrap\">\n            <a href=\"/api/attachments/${encodeURIComponent(att.id)}/download?token=${encodeURIComponent(this.app.authToken || '')}\" target=\"_blank\" class=\"btn btn-xs btn-outline btn-att-download\" title=\"View or Download file\">\n              <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>\n            </a>\n            <button type=\"button\" class=\"btn btn-xs btn-outline btn-att-delete\" title=\"Delete attachment\" style=\"color: #ef4444;\">\n              <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>\n            </button>\n          </div>\n        `;\n\n        item.querySelector('.btn-att-delete').addEventListener('click', (e) => {\n          e.stopPropagation();\n          this.deleteAttachment(att.id, att.filename);\n        });\n\n        listEl.appendChild(item);\n      });\n    } catch (err) {\n      listEl.innerHTML = `<span class=\"text-xs text-danger\">Error: ${this.escapeHtml(err.message)}</span>`;\n    }\n  }\n\n  async uploadAttachmentFile(file) {\n    if (!this.activeNode) return;\n    const nodeId = this.activeNode.id;\n    const listEl = document.getElementById('inspectorAttachmentsList');\n\n    if (listEl) {\n      const uploadingEl = document.createElement('div');\n      uploadingEl.className = 'attachment-item-card uploading';\n      uploadingEl.innerHTML = `<span class=\"text-xs text-muted\">Uploading <strong>${this.escapeHtml(file.name)}</strong>...</span>`;\n      listEl.prepend(uploadingEl);\n    }\n\n    try {\n      const res = await this.app.apiFetch(`/api/elements/${encodeURIComponent(nodeId)}/attachments`, {\n        method: 'POST',\n        headers: {\n          'X-Filename': encodeURIComponent(file.name),\n          'Content-Type': file.type || 'application/octet-stream',\n        },\n        body: file,\n      });\n\n      if (!res.ok) {\n        const errText = await res.text();\n        throw new Error(errText || 'Upload failed');\n      }\n\n      const att = await res.json();\n      this.app.showNotification(`Attached '${file.name}' successfully!`, 'success');\n\n      // Update workspace node attachment count & canvas badge\n      if (this.activeNode && this.activeNode.id === nodeId) {\n        this.activeNode.attachment_count = (this.activeNode.attachment_count || 0) + 1;\n      }\n      this.app.canvas.render();\n      await this.loadAttachments(nodeId);\n    } catch (err) {\n      alert(`Could not upload ${file.name}: ${err.message}`);\n      await this.loadAttachments(nodeId);\n    }\n  }\n\n  async deleteAttachment(attId, filename) {\n    if (!confirm(`Delete attachment '${filename}'?`)) return;\n\n    try {\n      const res = await this.app.apiFetch(`/api/attachments/${encodeURIComponent(attId)}`, {\n        method: 'DELETE',\n      });\n\n      if (!res.ok) throw new Error('Failed to delete attachment');\n\n      this.app.showNotification(`Deleted attachment '${filename}'.`, 'info');\n      if (this.activeNode) {\n        if (this.activeNode.attachment_count > 0) {\n          this.activeNode.attachment_count--;\n        }\n        this.app.canvas.render();\n        await this.loadAttachments(this.activeNode.id);\n      }\n    } catch (err) {\n      alert(`Error deleting attachment: ${err.message}`);\n    }\n  }\n\n  formatFileSize(bytes) {\n    if (!bytes || bytes === 0) return '0 B';\n    const k = 1024;\n    const sizes = ['B', 'KB', 'MB', 'GB'];\n    const i = Math.floor(Math.log(bytes) / Math.log(k));\n    return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];\n  }\n\n  getFileIconHtml(filename) {\n    const ext = (filename.split('.').pop() || '').toLowerCase();\n    if (ext === 'pdf') {\n      return `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/><line x1=\"9\" y1=\"13\" x2=\"15\" y2=\"13\"/></svg>`;\n    } else if (['doc', 'docx'].includes(ext)) {\n      return `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2563eb\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"/></svg>`;\n    } else if (['xls', 'xlsx', 'csv'].includes(ext)) {\n      return `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#10b981\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/><line x1=\"3\" y1=\"15\" x2=\"21\" y2=\"15\"/><line x1=\"9\" y1=\"3\" x2=\"9\" y2=\"21\"/><line x1=\"15\" y1=\"3\" x2=\"15\" y2=\"21\"/></svg>`;\n    } else if (['png', 'jpg', 'jpeg', 'svg'].includes(ext)) {\n      return `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#8b5cf6\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>`;\n    }\n    return `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#64748b\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>`;\n  }\n\n  escapeHtml(text) {\n    if (!text) return '';\n    return String(text)\n      .replace(/&/g, '&amp;')\n      .replace(/</g, '&lt;')\n      .replace(/>/g, '&gt;')\n      .replace(/\"/g, '&quot;')\n      .replace(/'/g, '&#039;');\n  }\n}\n",
    "js/layout.js": "// Bowtie Layout Engine: Natural Bowtie Geometry & Force/Momentum Spring Physics Animation\nclass BowtieLayoutEngine {\n  constructor(app) {\n    this.app = app;\n    this.padX = 35;\n    this.padY = 25;\n    this.animFrameId = null;\n    this.isAnimating = false;\n  }\n\n  // ==========================================================================\n  // 0. CLUSTER TOPOLOGY\n  // A diagram may contain several Hazard / Top Event clusters. Every node\n  // belongs to exactly one cluster, resolved by walking its parent chain up to\n  // a Top Event. This is the single source of truth used by both the layout\n  // solver and the connection renderer, so wiring and positioning always agree.\n  // ==========================================================================\n  static isRightWingType(type) {\n    return type === 'outcome_barrier' || type === 'outcome';\n  }\n\n  // Candidate whose Y is closest to the reference node's Y.\n  nearestByY(node, candidates) {\n    let best = null;\n    let bestDist = Infinity;\n    candidates.forEach(c => {\n      const d = Math.abs((node.y || 0) - (c.y || 0));\n      if (d < bestDist) { bestDist = d; best = c; }\n    });\n    return best;\n  }\n\n  // Returns Map<nodeId, topEventId> covering every node in the diagram.\n  buildClusterOwnership(diagNodes, topEvents) {\n    const ownership = new Map();\n    if (topEvents.length === 0) return ownership;\n\n    if (topEvents.length === 1) {\n      diagNodes.forEach(n => ownership.set(n.id, topEvents[0].id));\n      return ownership;\n    }\n\n    const byId = new Map(diagNodes.map(n => [n.id, n]));\n    const teByHazardId = new Map();\n    topEvents.forEach(te => {\n      ownership.set(te.id, te.id);\n      if (te.parent_id) teByHazardId.set(te.parent_id, te.id);\n    });\n\n    const resolve = (node) => {\n      const path = [];\n      const seen = new Set();\n      let curr = node;\n      let owner = null;\n      while (curr && !seen.has(curr.id)) {\n        if (ownership.has(curr.id)) { owner = ownership.get(curr.id); break; }\n        if (curr.node_type === 'hazard' && teByHazardId.has(curr.id)) {\n          owner = teByHazardId.get(curr.id);\n          break;\n        }\n        seen.add(curr.id);\n        path.push(curr);\n        curr = curr.parent_id ? byId.get(curr.parent_id) : null;\n      }\n      // Orphans (missing / cyclic parents) attach to the vertically nearest cluster\n      if (!owner) {\n        const nearest = this.nearestByY(node, topEvents);\n        owner = nearest ? nearest.id : topEvents[0].id;\n      }\n      path.forEach(p => ownership.set(p.id, owner));\n      return owner;\n    };\n\n    diagNodes.forEach(n => { if (!ownership.has(n.id)) resolve(n); });\n    return ownership;\n  }\n\n  // Helper to resolve the owning Top Event for any node in the diagram.\n  getTopEventForNode(node, diagNodes) {\n    if (!node) return null;\n    if (node.node_type === 'top_event') return node;\n    const topEvents = diagNodes.filter(n => n.node_type === 'top_event');\n    if (topEvents.length === 0) return null;\n    if (topEvents.length === 1) return topEvents[0];\n    const ownership = this.buildClusterOwnership(diagNodes, topEvents);\n    const teId = ownership.get(node.id);\n    return topEvents.find(te => te.id === teId) || this.nearestByY(node, topEvents) || topEvents[0];\n  }\n\n  // Threats feeding into the given Top Event, ordered top-to-bottom.\n  getClusterThreats(topEvent, diagNodes, ownership) {\n    return diagNodes\n      .filter(n => n.node_type === 'threat' && ownership.get(n.id) === topEvent.id)\n      .sort((a, b) => (a.y || 0) - (b.y || 0));\n  }\n\n  // Resolves the linear horizontal outcome branches for a Top Event cluster.\n  // Guarantees that outcome barriers in the same branch stay on the same horizontal row,\n  // preventing barriers from vertically stacking above or below the branch outcome.\n  getClusterRightBranches(topEvent, diagNodes, ownership) {\n    const clusterRightNodes = diagNodes.filter(n =>\n      BowtieLayoutEngine.isRightWingType(n.node_type) &&\n      (ownership ? ownership.get(n.id) === topEvent.id : true)\n    );\n\n    const outcomes = clusterRightNodes\n      .filter(n => n.node_type === 'outcome')\n      .sort((a, b) => (a.y || 0) - (b.y || 0));\n    const barriers = clusterRightNodes.filter(n => n.node_type === 'outcome_barrier');\n\n    const byId = new Map(diagNodes.map(n => [n.id, n]));\n    const branches = [];\n    const assignedBarrierIds = new Set();\n\n    if (outcomes.length > 0) {\n      outcomes.forEach(outcome => {\n        const branchBarriers = [];\n\n        // 1. Collect all barriers hierarchically belonging to this outcome (outcome > barriers, running right-to-left)\n        const queue = [outcome.id];\n        const visitedIds = new Set([outcome.id]);\n        while (queue.length > 0) {\n          const currentParentId = queue.shift();\n          barriers.forEach(b => {\n            if (!visitedIds.has(b.id) && b.parent_id === currentParentId) {\n              visitedIds.add(b.id);\n              if (!assignedBarrierIds.has(b.id)) {\n                branchBarriers.push(b);\n                assignedBarrierIds.add(b.id);\n                queue.push(b.id);\n              }\n            }\n          });\n        }\n\n        // 2. Legacy backwards-compatibility: walk parent chain up from outcome if outcome had parent_id = barrier\n        let curr = outcome.parent_id ? byId.get(outcome.parent_id) : null;\n        const legacySeen = new Set();\n        while (curr && !legacySeen.has(curr.id) && curr.node_type === 'outcome_barrier') {\n          legacySeen.add(curr.id);\n          if (!assignedBarrierIds.has(curr.id)) {\n            branchBarriers.push(curr);\n            assignedBarrierIds.add(curr.id);\n          }\n          curr = curr.parent_id ? byId.get(curr.parent_id) : null;\n        }\n\n        // 3. Assign any remaining unassigned barriers to the single or closest outcome by Y\n        barriers.forEach(b => {\n          if (!assignedBarrierIds.has(b.id)) {\n            if (outcomes.length === 1) {\n              branchBarriers.push(b);\n              assignedBarrierIds.add(b.id);\n            } else {\n              const closest = outcomes.reduce((best, oc) => {\n                const dBest = Math.abs((b.y || 0) - (best.y || 0));\n                const dCurr = Math.abs((b.y || 0) - (oc.y || 0));\n                return dCurr < dBest ? oc : best;\n              }, outcomes[0]);\n\n              if (closest.id === outcome.id) {\n                branchBarriers.push(b);\n                assignedBarrierIds.add(b.id);\n              }\n            }\n          }\n        });\n\n        // Sort barriers horizontally from left to right (by X position)\n        branchBarriers.sort((a, b) => (a.x || 0) - (b.x || 0));\n\n        const chain = [...branchBarriers, outcome];\n        const root = chain[0];\n        const branchY = (outcome.y !== undefined && outcome.y !== null) ? outcome.y : (root.y || 0);\n\n        branches.push({\n          root,\n          outcome,\n          chain,\n          branchY,\n          height: this.getBranchHeight(chain, diagNodes),\n        });\n      });\n    }\n\n    // Remaining unassigned barriers (when cluster has barriers but no outcomes yet)\n    const remainingBarriers = barriers\n      .filter(b => !assignedBarrierIds.has(b.id))\n      .sort((a, b) => (a.y || 0) - (b.y || 0));\n\n    if (remainingBarriers.length > 0) {\n      const rows = [];\n      remainingBarriers.forEach(b => {\n        let placedRow = rows.find(r => Math.abs((r[0].y || 0) - (b.y || 0)) < 60);\n        if (placedRow) {\n          placedRow.push(b);\n        } else {\n          rows.push([b]);\n        }\n      });\n\n      rows.forEach(row => {\n        row.sort((a, b) => (a.x || 0) - (b.x || 0));\n        const root = row[0];\n        branches.push({\n          root,\n          outcome: null,\n          chain: row,\n          branchY: root.y || 0,\n          height: this.getBranchHeight(row, diagNodes),\n        });\n      });\n    }\n\n    // Sort branches strictly ascending by branchY\n    branches.sort((a, b) => (a.branchY || 0) - (b.branchY || 0));\n    return branches;\n  }\n\n  // Heads of each outcome branch for the given Top Event.\n  getClusterRightRoots(topEvent, diagNodes, ownership) {\n    return this.getClusterRightBranches(topEvent, diagNodes, ownership).map(b => b.root);\n  }\n\n  // Hazard drawn above the given Top Event (its parent, or the nearest unclaimed hazard).\n  getClusterHazard(topEvent, diagNodes, claimedHazardIds = new Set()) {\n    const direct = diagNodes.find(n => n.id === topEvent.parent_id && n.node_type === 'hazard');\n    if (direct) return direct;\n    const candidates = diagNodes.filter(n =>\n      n.node_type === 'hazard' &&\n      !claimedHazardIds.has(n.id) &&\n      !diagNodes.some(te => te.node_type === 'top_event' && te.parent_id === n.id)\n    );\n    return this.nearestByY(topEvent, candidates) || null;\n  }\n\n  // ==========================================================================\n  // 1. MATHEMATICAL BOWTIE LAYOUT SOLVER\n  // Calculates optimal (tx, ty) coordinates for all nodes in the diagram\n  // ==========================================================================\n  // Calculate horizontal footprint of an element including all its escalation factors and mitigations\n  getItemSubtreeWidth(item, diagNodes) {\n    const efs = diagNodes\n      .filter(n => n.parent_id === item.id && n.node_type === 'escalation_factor')\n      .sort((a, b) => (a.y || 0) - (b.y || 0));\n    const baseW = item.width || 135;\n    if (efs.length === 0) return baseW;\n\n    let maxW = baseW;\n    efs.forEach(ef => {\n      const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n      const efW = ef.width || 130;\n      const isVert = this.isVerticalMitigationStack(mits);\n      const rowW = efW + (mits.length > 0 ? (isVert ? 25 + (mits[0].width || 120) : 25 + mits.length * 145) : 0);\n      maxW = Math.max(maxW, rowW);\n    });\n    return maxW;\n  }\n\n  // Mitigations under an Escalation Factor are laid out as a horizontal\n  // row extending outward from the Escalation Factor (standard Bowtie layout).\n  isVerticalMitigationStack(mits) {\n    // In standard Bowtie diagrams, mitigations extend horizontally outwards\n    // from the escalation factor so multiple escalation factors stack cleanly below.\n    return false;\n  }\n\n  // Vertical space consumed by the escalation-factor stack hanging below an item.\n  getEscalationStackHeight(item, diagNodes) {\n    const efs = diagNodes.filter(n => n.parent_id === item.id && n.node_type === 'escalation_factor');\n    return efs.reduce((acc, ef) => {\n      const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n      return acc + (this.isVerticalMitigationStack(mits) ? Math.max(98, (mits.length * 95) + 15) : 98);\n    }, 0);\n  }\n\n  // Row height of a branch: the tallest item (card + its escalation stack).\n  getBranchHeight(items, diagNodes) {\n    return items.reduce((maxH, item) => {\n      const efH = this.getEscalationStackHeight(item, diagNodes);\n      return efH > 0 ? Math.max(maxH, (item.height || 80) + 25 + efH) : maxH;\n    }, 80);\n  }\n\n  computeBowtieLayoutTargets(diagId) {\n    const allNodes = this.app.workspace.nodes;\n    const diagNodes = allNodes.filter(n => n.diagram_id === diagId);\n    if (diagNodes.length === 0) return new Map();\n\n    const targets = new Map(); // id -> { tx, ty }\n\n    const topEvents = diagNodes.filter(n => n.node_type === 'top_event');\n    if (topEvents.length === 0) return targets;\n\n    // Sort top events by their vertical Y position to preserve user ordering\n    topEvents.sort((a, b) => (a.y || 0) - (b.y || 0));\n\n    // Common center X across all top events in diagram\n    const cx = (topEvents[0].x !== undefined && topEvents[0].x !== null) ? topEvents[0].x : 680;\n\n    const ownership = this.buildClusterOwnership(diagNodes, topEvents);\n    const claimedHazardIds = new Set();\n    let prevClusterBottom = null;\n\n    topEvents.forEach((topEvent, clusterIdx) => {\n      const teW = topEvent.width || 130;\n      const teH = topEvent.height || 130;\n\n      const hazard = this.getClusterHazard(topEvent, diagNodes, claimedHazardIds);\n      if (hazard) claimedHazardIds.add(hazard.id);\n      const hW = hazard ? (hazard.width || 150) : 150;\n      const hH = hazard ? (hazard.height || 90) : 90;\n\n      // ------------------------------------------------------------------------\n      // LEFT WING: Threats & Pre-Event Barriers for this cluster\n      // ------------------------------------------------------------------------\n      const leftBranches = this.getClusterThreats(topEvent, diagNodes, ownership).map(threat => {\n        const barriers = this.getLinearChainForThreat(threat, diagNodes);\n        return { threat, barriers, height: this.getBranchHeight(barriers, diagNodes) };\n      });\n\n      const branchGapY = 40;\n      const totalLeftH = leftBranches.reduce((acc, b) => acc + b.height, 0) + \n                         Math.max(0, leftBranches.length - 1) * branchGapY;\n\n      // ------------------------------------------------------------------------\n      // RIGHT WING: Post-Event Outcome Barriers & Outcomes for this cluster\n      // Each branch yields exactly one horizontal row; barriers and outcomes\n      // in the same branch are placed in a horizontal sequence on that row.\n      // ------------------------------------------------------------------------\n      const rightBranches = this.getClusterRightBranches(topEvent, diagNodes, ownership);\n\n      const totalRightH = rightBranches.reduce((acc, b) => acc + b.height, 0) + \n                          Math.max(0, rightBranches.length - 1) * branchGapY;\n\n      // Determine clean cy for this cluster\n      let cy = (topEvent.y !== undefined && topEvent.y !== null) ? topEvent.y : (380 + clusterIdx * 500);\n\n      // Height of this cluster above top event center\n      const clusterTopOffset = Math.max(\n        hazard ? (hH + 50) : 0,\n        totalLeftH > 0 ? (totalLeftH - teH) / 2 : 0,\n        totalRightH > 0 ? (totalRightH - teH) / 2 : 0,\n        0\n      );\n\n      // If there was a previous cluster above, ensure no vertical collision\n      if (prevClusterBottom !== null) {\n        const minAllowedCy = prevClusterBottom + 80 + clusterTopOffset;\n        cy = Math.max(cy, minAllowedCy);\n      }\n\n      // Height of this cluster below top event center\n      const clusterBottomOffset = Math.max(\n        teH,\n        totalLeftH > 0 ? (teH / 2 + totalLeftH / 2) : teH,\n        totalRightH > 0 ? (teH / 2 + totalRightH / 2) : teH\n      );\n      prevClusterBottom = cy + clusterBottomOffset;\n\n      // Center Top Event and Hazard for this cluster\n      targets.set(topEvent.id, { tx: cx, ty: cy });\n\n      if (hazard) {\n        targets.set(hazard.id, {\n          tx: cx + (teW - hW) / 2,\n          ty: cy - hH - 50,\n        });\n      }\n\n      const leftTrunkX = cx - 55;\n      const rightTrunkX = cx + teW + 55;\n\n      // Place Left Wing\n      let currLeftY = cy + (teH / 2) - (totalLeftH / 2);\n      leftBranches.forEach(branch => {\n        const t = branch.threat;\n        const k = branch.barriers.length;\n        let currX = leftTrunkX;\n        const placedBoxes = [];\n\n        for (let i = k - 1; i >= 0; i--) {\n          const barrier = branch.barriers[i];\n          const bW = barrier.width || 135;\n          const bH = barrier.height || 80;\n          const efs = diagNodes\n            .filter(n => n.parent_id === barrier.id && n.node_type === 'escalation_factor')\n            .sort((a, b) => (a.y || 0) - (b.y || 0));\n\n          const computeBarrierBoxes = (barrX) => {\n            const boxes = [];\n            boxes.push({ id: barrier.id, x: barrX, y: currLeftY, w: bW, h: bH });\n\n            const efX = barrX + (bW - 130) / 2;\n            let currentEfY = currLeftY + bH + 25;\n            efs.forEach(ef => {\n              const efW = ef.width || 130;\n              const efH = ef.height || 80;\n              boxes.push({ id: ef.id, x: efX, y: currentEfY, w: efW, h: efH });\n\n              const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n              const isVert = this.isVerticalMitigationStack(mits);\n\n              if (isVert) {\n                const sortedMits = [...mits].sort((a, b) => (a.y || 0) - (b.y || 0));\n                sortedMits.forEach((mit, m) => {\n                  const mitW = mit.width || 120;\n                  const mitH = mit.height || 80;\n                  const mitX = efX - 25 - mitW;\n                  const mitY = currentEfY + (m * 95);\n                  boxes.push({ id: mit.id, x: mitX, y: mitY, w: mitW, h: mitH });\n                });\n                currentEfY += Math.max(98, (mits.length * 95) + 15);\n              } else {\n                const sortedMits = [...mits].sort((a, b) => b.x - a.x);\n                sortedMits.forEach((mit, m) => {\n                  const mitW = mit.width || 120;\n                  const mitH = mit.height || 80;\n                  const mitX = efX - 25 - mitW - (m * (mitW + 25));\n                  boxes.push({ id: mit.id, x: mitX, y: currentEfY, w: mitW, h: mitH });\n                });\n                currentEfY += 98;\n              }\n            });\n            return boxes;\n          };\n\n          let candidateX = currX - 45 - bW;\n          let candidateBoxes = computeBarrierBoxes(candidateX);\n\n          let collision = true;\n          let safetyIter = 0;\n          while (collision && safetyIter < 20) {\n            safetyIter++;\n            collision = false;\n            for (const newBox of candidateBoxes) {\n              for (const oldBox of placedBoxes) {\n                const yOverlap = Math.max(newBox.y, oldBox.y) < Math.min(newBox.y + newBox.h, oldBox.y + oldBox.h);\n                if (yOverlap) {\n                  const minGap = (newBox.id === barrier.id && oldBox.y === currLeftY) ? 45 : 25;\n                  if (newBox.x + newBox.w + minGap > oldBox.x) {\n                    const neededShift = (newBox.x + newBox.w + minGap) - oldBox.x;\n                    candidateX -= neededShift;\n                    candidateBoxes = computeBarrierBoxes(candidateX);\n                    collision = true;\n                    break;\n                  }\n                }\n              }\n              if (collision) break;\n            }\n          }\n\n          targets.set(barrier.id, { tx: candidateX, ty: currLeftY });\n          const finalBoxes = computeBarrierBoxes(candidateX);\n          finalBoxes.forEach(b => {\n            if (b.id !== barrier.id) {\n              targets.set(b.id, { tx: b.x, ty: b.y });\n            }\n            placedBoxes.push(b);\n          });\n\n          currX = candidateX;\n        }\n\n        const tW = t.width || 140;\n        const tX = currX - 45 - tW;\n        targets.set(t.id, { tx: tX, ty: currLeftY });\n\n        currLeftY += branch.height + branchGapY;\n      });\n\n      // Place Right Wing\n      let currRightY = cy + (teH / 2) - (totalRightH / 2);\n      rightBranches.forEach(branch => {\n        let chX = rightTrunkX + 45;\n        const placedBoxes = [];\n\n        branch.chain.forEach(item => {\n          const iW = item.width || 135;\n          const iH = item.height || 80;\n          const efs = diagNodes\n            .filter(n => n.parent_id === item.id && n.node_type === 'escalation_factor')\n            .sort((a, b) => (a.y || 0) - (b.y || 0));\n\n          const computeItemBoxes = (itemX) => {\n            const boxes = [];\n            boxes.push({ id: item.id, x: itemX, y: currRightY, w: iW, h: iH });\n\n            const efX = itemX + (iW - 130) / 2;\n            let currentEfY = currRightY + iH + 25;\n            efs.forEach(ef => {\n              const efW = ef.width || 130;\n              const efH = ef.height || 80;\n              boxes.push({ id: ef.id, x: efX, y: currentEfY, w: efW, h: efH });\n\n              const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n              const isVert = this.isVerticalMitigationStack(mits);\n\n              if (isVert) {\n                const sortedMits = [...mits].sort((a, b) => (a.y || 0) - (b.y || 0));\n                sortedMits.forEach((mit, m) => {\n                  const mitW = mit.width || 120;\n                  const mitH = mit.height || 80;\n                  const mitX = efX + efW + 25;\n                  const mitY = currentEfY + (m * 95);\n                  boxes.push({ id: mit.id, x: mitX, y: mitY, w: mitW, h: mitH });\n                });\n                currentEfY += Math.max(98, (mits.length * 95) + 15);\n              } else {\n                const sortedMits = [...mits].sort((a, b) => a.x - b.x);\n                sortedMits.forEach((mit, m) => {\n                  const mitW = mit.width || 120;\n                  const mitH = mit.height || 80;\n                  const mitX = efX + efW + 25 + (m * (mitW + 25));\n                  boxes.push({ id: mit.id, x: mitX, y: currentEfY, w: mitW, h: mitH });\n                });\n                currentEfY += 98;\n              }\n            });\n            return boxes;\n          };\n\n          let candidateX = chX;\n          let candidateBoxes = computeItemBoxes(candidateX);\n\n          let collision = true;\n          let safetyIter = 0;\n          while (collision && safetyIter < 20) {\n            safetyIter++;\n            collision = false;\n            for (const newBox of candidateBoxes) {\n              for (const oldBox of placedBoxes) {\n                const yOverlap = Math.max(newBox.y, oldBox.y) < Math.min(newBox.y + newBox.h, oldBox.y + oldBox.h);\n                if (yOverlap) {\n                  const minGap = (newBox.id === item.id && oldBox.y === currRightY) ? 45 : 25;\n                  if (newBox.x < oldBox.x + oldBox.w + minGap) {\n                    const neededShift = (oldBox.x + oldBox.w + minGap) - newBox.x;\n                    candidateX += neededShift;\n                    candidateBoxes = computeItemBoxes(candidateX);\n                    collision = true;\n                    break;\n                  }\n                }\n              }\n              if (collision) break;\n            }\n          }\n\n          targets.set(item.id, { tx: candidateX, ty: currRightY });\n          const finalBoxes = computeItemBoxes(candidateX);\n          finalBoxes.forEach(b => {\n            if (b.id !== item.id) {\n              targets.set(b.id, { tx: b.x, ty: b.y });\n            }\n            placedBoxes.push(b);\n          });\n\n          chX = candidateX + iW + 45;\n        });\n\n        currRightY += branch.height + branchGapY;\n      });\n    });\n\n    return targets;\n  }\n\n  // Get linear barrier chain attached to a Threat\n  getLinearChainForThreat(threat, allNodes) {\n    const barriers = [];\n    const visited = new Set();\n    const threatBarriers = allNodes.filter(\n      n => n.node_type === 'threat_barrier' && n.diagram_id === threat.diagram_id\n    );\n\n    const roots = threatBarriers\n      .filter(n => n.parent_id === threat.id)\n      .sort((a, b) => (a.x || 0) - (b.x || 0) || (a.created_at || '').localeCompare(b.created_at || ''));\n\n    const traverse = (node) => {\n      if (visited.has(node.id)) return;\n      visited.add(node.id);\n      barriers.push(node);\n\n      const nextBarriers = threatBarriers\n        .filter(n => n.parent_id === node.id)\n        .sort((a, b) => (a.x || 0) - (b.x || 0));\n      nextBarriers.forEach(traverse);\n    };\n\n    roots.forEach(traverse);\n    return barriers;\n  }\n\n  // Get linear chain along an Outcome branch\n  getLinearChainForOutcome(rootNode, allNodes) {\n    const chain = [];\n    const visited = new Set();\n\n    const traverse = (node) => {\n      if (visited.has(node.id)) return;\n      visited.add(node.id);\n      chain.push(node);\n\n      const nextChildren = allNodes\n        .filter(n => n.parent_id === node.id && (n.node_type === 'outcome_barrier' || n.node_type === 'outcome'))\n        .sort((a, b) => {\n          if (a.node_type === 'outcome' && b.node_type !== 'outcome') return 1;\n          if (b.node_type === 'outcome' && a.node_type !== 'outcome') return -1;\n          return (a.x || 0) - (b.x || 0);\n        });\n\n      nextChildren.forEach(traverse);\n    };\n\n    traverse(rootNode);\n\n    // Ensure barriers come before terminal outcomes in horizontal sequence\n    const barriers = chain.filter(n => n.node_type === 'outcome_barrier');\n    const outcomes = chain.filter(n => n.node_type === 'outcome');\n    return [...barriers, ...outcomes];\n  }\n\n  // ==========================================================================\n  // 2. FORCE & MOMENTUM SPRING PHYSICS ANIMATION\n  // Smooth, professional deceleration into balanced bowtie positions\n  // ==========================================================================\n  animateToBowtieLayout(diagId, options = {}) {\n    const newlyAddedNode = options.newlyAddedNode || null;\n    const onComplete = options.onComplete || null;\n\n    if (this.animFrameId) {\n      cancelAnimationFrame(this.animFrameId);\n      this.animFrameId = null;\n    }\n\n    const diagNodes = this.app.workspace.nodes.filter(n => n.diagram_id === diagId);\n    if (diagNodes.length === 0) return;\n\n    // Calculate balanced targets for the entire bowtie diagram\n    const targetMap = this.computeBowtieLayoutTargets(diagId);\n    if (targetMap.size === 0) return;\n\n    // Initialize velocity and target coordinates on nodes\n    diagNodes.forEach(node => {\n      const tgt = targetMap.get(node.id);\n      if (tgt) {\n        node.tx = tgt.tx;\n        node.ty = tgt.ty;\n      } else {\n        node.tx = node.x;\n        node.ty = node.y;\n      }\n\n      if (node.vx === undefined) node.vx = 0;\n      if (node.vy === undefined) node.vy = 0;\n\n      // If node was just added, spawn near parent with initial outward momentum\n      if (newlyAddedNode && node.id === newlyAddedNode.id) {\n        const parent = this.app.getNodeById(node.parent_id);\n        if (parent) {\n          node.x = parent.x + 30;\n          node.y = parent.y + 20;\n          // Initial velocity impulse toward target\n          node.vx = (node.tx - node.x) * 0.18;\n          node.vy = (node.ty - node.y) * 0.18;\n        }\n      }\n    });\n\n    this.isAnimating = true;\n\n    // Physics parameters for responsive force and smooth momentum deceleration\n    const stiffness = 0.18; // Spring pulling strength\n    const damping = 0.72;   // Momentum deceleration / friction factor\n\n    let frameCount = 0;\n    const maxFrames = 90; // Safety cap ~1.5s\n\n    const step = () => {\n      frameCount++;\n      let maxDist = 0;\n      let maxVel = 0;\n\n      diagNodes.forEach(node => {\n        // Skip nodes currently being manually dragged by user\n        if (this.app.canvas.draggedNodeId === node.id) return;\n\n        const dx = node.tx - node.x;\n        const dy = node.ty - node.y;\n\n        // Hooke's spring force\n        const fx = dx * stiffness;\n        const fy = dy * stiffness;\n\n        // Apply force to velocity and momentum decay\n        node.vx = (node.vx + fx) * damping;\n        node.vy = (node.vy + fy) * damping;\n\n        node.x += node.vx;\n        node.y += node.vy;\n\n        const dist = Math.hypot(dx, dy);\n        const vel = Math.hypot(node.vx, node.vy);\n\n        if (dist > maxDist) maxDist = dist;\n        if (vel > maxVel) maxVel = vel;\n\n        // Directly update DOM position for smooth 60 FPS transform\n        const el = document.getElementById(`node-${node.id}`);\n        if (el) {\n          el.style.left = `${Math.round(node.x)}px`;\n          el.style.top = `${Math.round(node.y)}px`;\n        }\n      });\n\n      // Update connection wires at every animation step\n      this.app.canvas.renderConnections();\n\n      // Check convergence: when force has settled and velocity has slowed to rest\n      if ((maxDist < 0.6 && maxVel < 0.25) || frameCount >= maxFrames) {\n        // Snap cleanly to target\n        diagNodes.forEach(node => {\n          if (this.app.canvas.draggedNodeId !== node.id) {\n            node.x = Math.round(node.tx);\n            node.y = Math.round(node.ty);\n            node.vx = 0;\n            node.vy = 0;\n            const el = document.getElementById(`node-${node.id}`);\n            if (el) {\n              el.style.left = `${node.x}px`;\n              el.style.top = `${node.y}px`;\n            }\n          }\n        });\n\n        this.app.canvas.renderConnections();\n        this.isAnimating = false;\n        this.animFrameId = null;\n\n        if (onComplete) onComplete();\n        return;\n      }\n\n      this.animFrameId = requestAnimationFrame(step);\n    };\n\n    this.animFrameId = requestAnimationFrame(step);\n  }\n\n  // ==========================================================================\n  // 3. INITIAL NODE PLACEMENT CALCULATORS (Called when adding nodes)\n  // ==========================================================================\n  calculatePlacementForThreatBarrier(parentNode, allNodes) {\n    let node = parentNode;\n    while (node && (node.node_type === 'escalation_factor' || node.node_type === 'mitigation')) {\n      node = allNodes.find(n => n.id === node.parent_id);\n    }\n    if (!node) node = parentNode;\n\n    return {\n      x: node.x + (node.width || 140) + 30,\n      y: node.y,\n    };\n  }\n\n  calculatePlacementForOutcomeBarrier(parentNode, allNodes) {\n    let node = parentNode;\n    while (node && (node.node_type === 'escalation_factor' || node.node_type === 'mitigation')) {\n      node = allNodes.find(n => n.id === node.parent_id);\n    }\n    if (!node) node = parentNode;\n\n    if (node.node_type === 'outcome') {\n      return {\n        x: node.x,\n        y: node.y,\n      };\n    }\n    return {\n      x: node.x + (node.width || 135) + 30,\n      y: node.y,\n    };\n  }\n\n  calculatePlacementForEscalationFactor(parentNode, allNodes) {\n    const existingEfs = allNodes.filter(n => n.parent_id === parentNode.id && n.node_type === 'escalation_factor');\n    return {\n      x: parentNode.x + ((parentNode.width || 135) - 130) / 2,\n      y: parentNode.y + (parentNode.height || 80) + 25 + existingEfs.length * 98,\n    };\n  }\n\n  calculatePlacementForMitigation(parentNode, allNodes) {\n    const existingMits = allNodes.filter(n => n.parent_id === parentNode.id && n.node_type === 'mitigation');\n    const barrier = allNodes.find(n => n.id === parentNode.parent_id);\n    const topEvent = allNodes.find(n => n.node_type === 'top_event');\n    const isThreatSide = barrier \n      ? (barrier.node_type === 'threat_barrier' || (topEvent && barrier.x < topEvent.x))\n      : (topEvent && parentNode.x < topEvent.x);\n\n    const mitW = 120;\n    if (isThreatSide) {\n      return {\n        x: parentNode.x - 25 - mitW - (existingMits.length * (mitW + 25)),\n        y: parentNode.y,\n      };\n    } else {\n      return {\n        x: parentNode.x + (parentNode.width || 130) + 25 + (existingMits.length * (mitW + 25)),\n        y: parentNode.y,\n      };\n    }\n  }\n\n  calculateNewThreatPlacement(diagramId, allNodes, targetTopEvent = null) {\n    const diagNodes = allNodes.filter(n => n.diagram_id === diagramId);\n    const topEvent = targetTopEvent || diagNodes.find(n => n.node_type === 'top_event');\n    const teId = topEvent ? topEvent.id : null;\n    const teX = topEvent ? (topEvent.x || 680) : 680;\n    const baseY = topEvent ? (topEvent.y || 380) : 380;\n\n    const clusterThreats = teId\n      ? diagNodes.filter(n => n.node_type === 'threat' && n.parent_id === teId)\n      : diagNodes.filter(n => n.node_type === 'threat');\n\n    let maxY = baseY;\n    clusterThreats.forEach(t => {\n      maxY = Math.max(maxY, (t.y || 0) + (t.height || 80) + 40);\n    });\n\n    const defaultThreatX = teX - 350;\n    return {\n      x: clusterThreats.length > 0 ? clusterThreats[0].x : defaultThreatX,\n      y: maxY,\n    };\n  }\n\n  calculateNewOutcomePlacement(diagramId, allNodes, targetTopEvent = null) {\n    const diagNodes = allNodes.filter(n => n.diagram_id === diagramId);\n    const topEvents = diagNodes.filter(n => n.node_type === 'top_event');\n    const topEvent = targetTopEvent || (topEvents.length > 0 ? topEvents[0] : null);\n    const teX = topEvent ? (topEvent.x || 680) : 680;\n    const teW = topEvent ? (topEvent.width || 130) : 130;\n    const baseY = topEvent ? (topEvent.y || 380) : 380;\n\n    let maxY = baseY;\n    if (topEvent) {\n      const ownership = this.buildClusterOwnership(diagNodes, topEvents);\n      const branches = this.getClusterRightBranches(topEvent, diagNodes, ownership);\n      branches.forEach(b => {\n        maxY = Math.max(maxY, (b.branchY || 0) + (b.height || 80) + 40);\n      });\n    }\n\n    return {\n      x: teX + teW + 40,\n      y: maxY,\n    };\n  }\n\n  // ==========================================================================\n  // 4. MANUAL DRAG COLLISION PADDING RESOLVER\n  // Gently separates only overlapping cards without breaking manual positions\n  // ==========================================================================\n  resolveCollisions(diagramId, anchorIds = []) {\n    const diagNodes = this.app.workspace.nodes.filter(n => n.diagram_id === diagramId);\n    const anchorSet = new Set(anchorIds);\n    let shifted = false;\n\n    const padX = 25;\n    const padY = 25;\n\n    for (const anchorId of anchorIds) {\n      const a = diagNodes.find(n => n.id === anchorId);\n      if (!a) continue;\n\n      const aW = a.width || 135;\n      const aH = a.height || 80;\n\n      for (const other of diagNodes) {\n        if (anchorSet.has(other.id)) continue;\n\n        // Sibling mitigations under the same escalation factor reorder horizontally, do not push vertically\n        if (a.node_type === 'mitigation' && other.node_type === 'mitigation' && a.parent_id === other.parent_id) {\n          continue;\n        }\n\n        const oW = other.width || 135;\n        const oH = other.height || 80;\n\n        const overlapX = (a.x < other.x + oW + padX) && (a.x + aW + padX > other.x);\n        const overlapY = (a.y < other.y + oH + padY) && (a.y + aH + padY > other.y);\n\n        if (overlapX && overlapY) {\n          const pushDown = a.y <= other.y;\n          const shiftAmount = pushDown\n            ? (a.y + aH + padY) - other.y\n            : -((other.y + oH + padY) - a.y);\n\n          other.y += shiftAmount;\n          const el = document.getElementById(`node-${other.id}`);\n          if (el) el.style.top = `${other.y}px`;\n          shifted = true;\n        }\n      }\n    }\n\n    if (shifted) {\n      this.app.canvas.renderConnections();\n    }\n    return shifted;\n  }\n\n  // ==========================================================================\n  // 5. PUBLIC ACTIONS\n  // ==========================================================================\n  balanceDiagram(diagId) {\n    // Smoothly align items in place without moving or snapping the camera/viewport\n    this.animateToBowtieLayout(diagId);\n  }\n}\n",
    "js/simulator.js": "// Bowtie Active Situation Runner & Incident Simulator Engine\n// Supports both Real-Time (wall-clock 1s=1s with speedups) and Simulated (~30s proportional scaling)\nclass BowtieSimulator {\n  constructor(app) {\n    this.app = app;\n\n    // Simulation state\n    this.isRunning = false;\n    this.isPaused = false;\n    this.mode = 'simulated'; // 'simulated' (30s) or 'realtime'\n    this.speed = 1.0;        // 1x, 2x, 5x, 10x\n    this.scheduledDelaySec = 0; // 0, 5, 10\n    this.threatId = null;\n\n    // Time & plan tracking\n    this.plan = [];\n    this.currentStepIdx = 0;\n    this.stepProgress = 0.0;\n    this.elapsedSimSec = 0.0;\n    this.elapsedRealSec = 0.0;\n    this.totalPlanRealSec = 30.0;\n    this.targetSimDuration = 30.0;\n    this.scaleRatio = 1.0;\n\n    // Animation loop & visual elements\n    this.animFrameId = null;\n    this.lastTimestamp = null;\n    this.activeParticle = null;\n    this.activeParticlePath = null;\n    this.activeParticleLen = 0;\n    this.activeNodeBadges = new Map(); // nodeId -> badgeElement\n    this.eventLogs = [];\n\n    // DOM Elements\n    this.hud = document.getElementById('situationRunnerHud');\n    this.threatSelect = document.getElementById('simThreatSelect');\n    this.clockDisplay = document.getElementById('simClockDisplay');\n    this.metricSim = document.getElementById('simMetricSim');\n    this.metricReal = document.getElementById('simMetricReal');\n    this.metricStage = document.getElementById('simMetricStage');\n    this.progressFill = document.getElementById('simProgressFill');\n    this.statusPill = document.getElementById('simStatusPill');\n    this.modeTag = document.getElementById('simModeTag');\n    this.btnPlay = document.getElementById('btnSimPlay');\n    this.btnPause = document.getElementById('btnSimPause');\n    this.btnStep = document.getElementById('btnSimStep');\n    this.btnReset = document.getElementById('btnSimReset');\n    this.btnCollapse = document.getElementById('btnSimCollapse');\n    this.logDrawer = document.getElementById('simLogDrawer');\n    this.logList = document.getElementById('simLogList');\n    this.logCountBadge = document.getElementById('simLogCountBadge');\n\n    // Dragging & UI state\n    this._headerJustDragged = false;\n\n    // Realtime collaboration synchronization\n    this.isHandlingRemoteAction = false;\n    this.lastSyncBroadcast = 0;\n\n    this.initEvents();\n  }\n\n  broadcastSimAction(action, extra = {}) {\n    if (this.isHandlingRemoteAction) return;\n    if (!this.app || !this.app.currentDiagram) return;\n\n    const diagId = this.app.currentDiagram.id;\n    const userName = this.app.currentUser ? (this.app.currentUser.name || this.app.currentUser.email.split('@')[0]) : 'Collaborator';\n    const userColor = this.app.userColor || '#3b82f6';\n\n    const payload = {\n      diagram_id: diagId,\n      session_id: this.app.sessionId,\n      user_name: userName,\n      user_color: userColor,\n      action: action,\n      threat_id: extra.threat_id !== undefined ? extra.threat_id : (this.threatId || null),\n      mode: extra.mode !== undefined ? extra.mode : (this.mode || 'simulated'),\n      speed: extra.speed !== undefined ? extra.speed : (this.speed || 1.0),\n      scheduled_delay: extra.scheduled_delay !== undefined ? extra.scheduled_delay : (this.scheduledDelaySec || 0.0),\n      step_idx: extra.step_idx !== undefined ? extra.step_idx : (this.currentStepIdx || 0),\n      elapsed_sim_sec: extra.elapsed_sim_sec !== undefined ? extra.elapsed_sim_sec : (this.elapsedSimSec || 0.0),\n      step_progress: extra.step_progress !== undefined ? extra.step_progress : (this.stepProgress || 0.0),\n    };\n\n    this.app.apiFetch('/api/realtime/broadcast', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        diagram_id: diagId,\n        message: {\n          type: 'sim_action',\n          data: payload,\n        }\n      })\n    }).catch(() => {});\n  }\n\n  handleRemoteAction(data) {\n    if (!data || !data.action) return;\n\n    this.isHandlingRemoteAction = true;\n    try {\n      const action = data.action;\n\n      if (action === 'run') {\n        this.openHud();\n        if (data.threat_id) {\n          this.threatId = data.threat_id;\n          if (this.threatSelect) this.threatSelect.value = data.threat_id;\n        }\n        if (data.mode) {\n          this.setMode(data.mode);\n          const btnSim = document.getElementById('btnSimModeSimulated');\n          const btnReal = document.getElementById('btnSimModeRealtime');\n          const schedRow = document.getElementById('simScheduleRow');\n          if (btnSim && btnReal) {\n            if (data.mode === 'simulated') {\n              btnSim.classList.add('active');\n              btnReal.classList.remove('active');\n              if (schedRow) schedRow.style.display = 'none';\n            } else {\n              btnReal.classList.add('active');\n              btnSim.classList.remove('active');\n              if (schedRow) schedRow.style.display = 'flex';\n            }\n          }\n        }\n        if (data.speed != null) {\n          this.speed = data.speed;\n          const speedBtns = document.querySelectorAll('.sim-speed-btn');\n          speedBtns.forEach(b => {\n            b.classList.toggle('active', parseFloat(b.dataset.speed || '1') === data.speed);\n          });\n        }\n        if (data.scheduled_delay != null) {\n          this.scheduledDelaySec = data.scheduled_delay;\n        }\n\n        if (this.isRunning) {\n          this.reset();\n        }\n        this.run();\n\n        if (data.elapsed_sim_sec != null && data.elapsed_sim_sec > 1.0) {\n          this.elapsedSimSec = data.elapsed_sim_sec;\n          this.elapsedRealSec = data.elapsed_sim_sec;\n          this.fastForwardToElapsed(data.elapsed_sim_sec);\n        }\n\n        if (this.app) {\n          this.app.showNotification(`${data.user_name || 'Collaborator'} started incident simulation`, 'info');\n        }\n      } else if (action === 'pause') {\n        if (this.isRunning && !this.isPaused) {\n          this.pause();\n        }\n      } else if (action === 'resume') {\n        if (!this.isRunning) {\n          this.openHud();\n          if (data.threat_id) {\n            this.threatId = data.threat_id;\n            if (this.threatSelect) this.threatSelect.value = data.threat_id;\n          }\n          this.run();\n        }\n        if (this.isPaused) {\n          this.resume();\n        }\n      } else if (action === 'step') {\n        if (!this.isRunning) {\n          this.openHud();\n          if (data.threat_id) {\n            this.threatId = data.threat_id;\n            if (this.threatSelect) this.threatSelect.value = data.threat_id;\n          }\n          this.run();\n          this.pause();\n        }\n        this.step();\n      } else if (action === 'reset') {\n        if (this.isRunning || this.isPaused) {\n          this.reset();\n        }\n      } else if (action === 'set_threat') {\n        if (data.threat_id) {\n          this.threatId = data.threat_id;\n          if (this.threatSelect) this.threatSelect.value = data.threat_id;\n          if (this.app && this.app.canvas) {\n            this.app.canvas.centerOnNode(data.threat_id);\n            this.app.canvas.setActiveNode(data.threat_id);\n          }\n        }\n      } else if (action === 'set_mode') {\n        if (data.mode) {\n          this.setMode(data.mode);\n        }\n      } else if (action === 'set_speed') {\n        if (data.speed != null) {\n          this.speed = data.speed;\n          const speedBtns = document.querySelectorAll('.sim-speed-btn');\n          speedBtns.forEach(b => {\n            b.classList.toggle('active', parseFloat(b.dataset.speed || '1') === data.speed);\n          });\n        }\n      } else if (action === 'sync') {\n        if (!this.isRunning) {\n          this.openHud();\n          if (data.threat_id) {\n            this.threatId = data.threat_id;\n            if (this.threatSelect) this.threatSelect.value = data.threat_id;\n          }\n          if (data.mode) this.setMode(data.mode);\n          if (data.speed != null) this.speed = data.speed;\n\n          this.compilePlan(this.threatId);\n          if (this.plan.length > 0) {\n            this.isRunning = true;\n            this.isPaused = false;\n            this.updateTransportUI('running');\n            this.clearVisualStates();\n\n            const threatEl = document.getElementById(`node-${this.threatId}`);\n            if (threatEl) {\n              threatEl.classList.add('is-sim-active-threat');\n              this.setNodeBadge(threatEl, '⚡ ACTIVE THREAT', 'badge-threat-active');\n            }\n\n            const elapsed = data.elapsed_sim_sec || 0;\n            if (elapsed > 0) {\n              this.elapsedSimSec = elapsed;\n              this.elapsedRealSec = elapsed;\n              this.fastForwardToElapsed(elapsed);\n            } else {\n              this.currentStepIdx = 0;\n              this.stepProgress = 0.0;\n              this.elapsedSimSec = 0.0;\n              this.elapsedRealSec = 0.0;\n              this.enterStep(this.plan[0]);\n            }\n\n            this.lastTimestamp = performance.now();\n            this.animFrameId = requestAnimationFrame((ts) => this.loop(ts));\n          }\n        } else if (!this.isPaused && data.elapsed_sim_sec != null) {\n          const drift = Math.abs(this.elapsedSimSec - data.elapsed_sim_sec);\n          if (drift > 2.0) {\n            this.elapsedSimSec = data.elapsed_sim_sec;\n            this.elapsedRealSec = data.elapsed_sim_sec;\n          }\n        }\n      }\n    } catch (err) {\n      console.warn('Error handling remote simulator action:', err);\n    } finally {\n      this.isHandlingRemoteAction = false;\n    }\n  }\n\n  initEvents() {\n    // Prevent touch events within Situation Runner HUD from bubbling to canvas pan/pinch\n    if (this.hud) {\n      ['touchstart', 'touchend'].forEach(evt => {\n        this.hud.addEventListener(evt, (e) => {\n          e.stopPropagation();\n        }, { passive: true });\n      });\n      this.hud.addEventListener('touchmove', (e) => {\n        e.stopPropagation();\n      }, { passive: true });\n    }\n\n    // Toolbar toggle button\n    const btnToggle = document.getElementById('btnToggleSituationRunner');\n    if (btnToggle) {\n      btnToggle.addEventListener('click', () => {\n        this.toggleHud();\n      });\n    }\n\n    // HUD Header close & collapse\n    const btnClose = document.getElementById('btnSimClose');\n    if (btnClose) {\n      const handleClose = (e) => {\n        e.preventDefault();\n        e.stopPropagation();\n        this.closeHud();\n      };\n      btnClose.addEventListener('click', handleClose);\n      btnClose.addEventListener('touchend', handleClose, { passive: false });\n    }\n\n    const btnCollapse = document.getElementById('btnSimCollapse');\n    if (btnCollapse) {\n      const handleCollapse = (e) => {\n        e.preventDefault();\n        e.stopPropagation();\n        this.toggleCollapse();\n      };\n      btnCollapse.addEventListener('click', handleCollapse);\n      btnCollapse.addEventListener('touchend', handleCollapse, { passive: false });\n    }\n\n    const simHeader = document.getElementById('simHudHeader');\n    if (simHeader) {\n      simHeader.addEventListener('click', (e) => {\n        if (this._headerJustDragged) {\n          this._headerJustDragged = false;\n          return;\n        }\n        if (e.target.closest('.sim-hud-header-actions')) return;\n        this.toggleCollapse();\n      });\n    }\n\n    this.initDraggable();\n\n    // Threat select\n    if (this.threatSelect) {\n      this.threatSelect.addEventListener('change', (e) => {\n        this.setThreat(e.target.value);\n      });\n    }\n\n    // Order barriers by time button in simulator HUD\n    const btnSimOrder = document.getElementById('btnSimOrderBarriers');\n    if (btnSimOrder) {\n      const handleSimOrder = (e) => {\n        e.preventDefault();\n        e.stopPropagation();\n        if (this.threatId) {\n          this.app.orderBarriersByTime(this.threatId);\n        } else {\n          this.app.orderBarriersByTime();\n        }\n      };\n      btnSimOrder.addEventListener('click', handleSimOrder);\n      btnSimOrder.addEventListener('touchend', handleSimOrder, { passive: false });\n    }\n\n    // Mode buttons\n    const btnSim = document.getElementById('btnSimModeSimulated');\n    const btnReal = document.getElementById('btnSimModeRealtime');\n    const schedRow = document.getElementById('simScheduleRow');\n\n    if (btnSim && btnReal) {\n      btnSim.addEventListener('click', () => {\n        btnSim.classList.add('active');\n        btnReal.classList.remove('active');\n        if (schedRow) schedRow.style.display = 'none';\n        this.setMode('simulated');\n      });\n\n      btnReal.addEventListener('click', () => {\n        btnReal.classList.add('active');\n        btnSim.classList.remove('active');\n        if (schedRow) schedRow.style.display = 'flex';\n        this.setMode('realtime');\n      });\n    }\n\n    // Speed multiplier buttons\n    const speedBtns = document.querySelectorAll('.sim-speed-btn');\n    speedBtns.forEach(btn => {\n      btn.addEventListener('click', () => {\n        speedBtns.forEach(b => b.classList.remove('active'));\n        btn.classList.add('active');\n        this.speed = parseFloat(btn.dataset.speed || '1');\n        if (!this.isHandlingRemoteAction && this.isRunning) {\n          this.broadcastSimAction('set_speed', { speed: this.speed });\n        }\n      });\n    });\n\n    // Schedule delay and past elapsed buttons\n    const schedBtns = document.querySelectorAll('.sim-sched-btn');\n    const pastElapsedBox = document.getElementById('simPastElapsedBox');\n    const btnSchedPast = document.getElementById('btnSchedPast');\n    const btnSchedImmediate = document.getElementById('btnSchedImmediate');\n    const elapsedHoursInput = document.getElementById('simElapsedHours');\n    const elapsedMinsInput = document.getElementById('simElapsedMins');\n\n    schedBtns.forEach(btn => {\n      btn.addEventListener('click', () => {\n        schedBtns.forEach(b => b.classList.remove('active'));\n        btn.classList.add('active');\n        if (btn === btnSchedPast) {\n          if (pastElapsedBox) pastElapsedBox.style.display = 'flex';\n          this.scheduledDelaySec = 0;\n        } else {\n          if (pastElapsedBox) pastElapsedBox.style.display = 'none';\n          this.scheduledDelaySec = parseFloat(btn.dataset.delay || '0');\n        }\n      });\n    });\n\n    if (btnSchedImmediate) {\n      btnSchedImmediate.addEventListener('click', () => {\n        if (pastElapsedBox) pastElapsedBox.style.display = 'none';\n        if (elapsedHoursInput) elapsedHoursInput.value = '0';\n        if (elapsedMinsInput) elapsedMinsInput.value = '0';\n      });\n    }\n\n    const quickPills = document.querySelectorAll('.sim-quick-pill');\n    quickPills.forEach(pill => {\n      pill.addEventListener('click', () => {\n        if (btnSchedPast) {\n          schedBtns.forEach(b => b.classList.remove('active'));\n          btnSchedPast.classList.add('active');\n        }\n        if (pastElapsedBox) pastElapsedBox.style.display = 'flex';\n        if (elapsedHoursInput) elapsedHoursInput.value = pill.dataset.h || '0';\n        if (elapsedMinsInput) elapsedMinsInput.value = pill.dataset.m || '0';\n      });\n    });\n\n    // Transport buttons\n    if (this.btnPlay) {\n      this.btnPlay.addEventListener('click', () => {\n        if (this.isPaused) {\n          this.resume();\n        } else {\n          this.run();\n        }\n      });\n    }\n\n    if (this.btnPause) {\n      this.btnPause.addEventListener('click', () => {\n        this.pause();\n      });\n    }\n\n    if (this.btnStep) {\n      this.btnStep.addEventListener('click', () => {\n        this.step();\n      });\n    }\n\n    if (this.btnReset) {\n      this.btnReset.addEventListener('click', () => {\n        this.reset();\n      });\n    }\n\n    // Incident Log drawer toggle\n    const btnToggleLog = document.getElementById('btnSimToggleLog');\n    if (btnToggleLog && this.logDrawer) {\n      btnToggleLog.addEventListener('click', () => {\n        const isHidden = (this.logDrawer.style.display === 'none');\n        this.logDrawer.style.display = isHidden ? 'flex' : 'none';\n      });\n    }\n\n    const btnClearLog = document.getElementById('btnSimClearLog');\n    if (btnClearLog) {\n      btnClearLog.addEventListener('click', () => this.clearLog());\n    }\n\n    const btnCopyLog = document.getElementById('btnSimCopyLog');\n    if (btnCopyLog) {\n      btnCopyLog.addEventListener('click', () => this.copyLogToClipboard());\n    }\n  }\n\n  // Populate Threat selector for the current active diagram\n  populateThreats() {\n    if (!this.threatSelect) return;\n    this.threatSelect.innerHTML = '<option value=\"\">-- Choose Threat to Trigger --</option>';\n\n    if (!this.app.workspace || !this.app.workspace.nodes) return;\n    const diagId = this.app.currentDiagram ? this.app.currentDiagram.id : null;\n    const diagNodes = this.app.workspace.nodes.filter(n => !diagId || n.diagram_id === diagId);\n    const threats = diagNodes.filter(n => n.node_type === 'threat');\n    const topEvents = diagNodes.filter(n => n.node_type === 'top_event');\n\n    // If an active node is currently selected on the canvas, resolve its threat/cluster\n    let targetThreatId = null;\n    if (this.app.canvas && this.app.canvas.activeNodeId) {\n      const activeNode = this.app.getNodeById(this.app.canvas.activeNodeId);\n      if (activeNode && (!diagId || activeNode.diagram_id === diagId)) {\n        if (activeNode.node_type === 'threat') {\n          targetThreatId = activeNode.id;\n        } else {\n          const activeTe = this.app.layout ? this.app.layout.getTopEventForNode(activeNode, diagNodes) : null;\n          if (activeTe) {\n            const currentThreat = threats.find(t => t.id === this.threatId);\n            const currentOwnerTe = (currentThreat && this.app.layout)\n              ? this.app.layout.getTopEventForNode(currentThreat, diagNodes)\n              : null;\n            if (currentOwnerTe && currentOwnerTe.id === activeTe.id) {\n              targetThreatId = this.threatId;\n            } else {\n              const clusterThreats = threats.filter(t => {\n                const ownerTe = this.app.layout ? this.app.layout.getTopEventForNode(t, diagNodes) : null;\n                return ownerTe ? ownerTe.id === activeTe.id : t.parent_id === activeTe.id;\n              });\n              if (clusterThreats.length > 0) {\n                targetThreatId = clusterThreats[0].id;\n              }\n            }\n          }\n        }\n      }\n    }\n\n    if (targetThreatId) {\n      this.threatId = targetThreatId;\n    } else if (this.threatId && !threats.some(t => t.id === this.threatId)) {\n      this.threatId = threats.length > 0 ? threats[0].id : null;\n    } else if (!this.threatId && threats.length > 0) {\n      this.threatId = threats[0].id;\n    }\n\n    // Populate dropdown with optgroups if multiple Top Events exist\n    if (topEvents.length > 1) {\n      topEvents.forEach(te => {\n        const hazard = this.app.layout\n          ? this.app.layout.getClusterHazard(te, diagNodes)\n          : diagNodes.find(h => h.id === te.parent_id && h.node_type === 'hazard');\n        const hTitle = hazard ? (this.app ? this.app.getNodeDisplayTitle(hazard) : (hazard.description || hazard.title || 'Hazard')) : 'Hazard';\n        const teTitle = te ? (this.app ? this.app.getNodeDisplayTitle(te) : (te.description || te.title || 'Top Event')) : 'Top Event';\n        const optgroup = document.createElement('optgroup');\n        optgroup.label = `${hTitle} → ${teTitle}`;\n\n        const clusterThreats = threats.filter(t => {\n          const ownerTe = this.app.layout ? this.app.layout.getTopEventForNode(t, diagNodes) : null;\n          return ownerTe ? ownerTe.id === te.id : t.parent_id === te.id;\n        });\n\n        clusterThreats.forEach(t => {\n          const opt = document.createElement('option');\n          opt.value = t.id;\n          const thTitle = this.app ? this.app.getNodeDisplayTitle(t) : (t.title || 'Threat');\n          opt.textContent = `${thTitle}: ${t.description || 'Untitled ' + thTitle}`;\n          if (this.threatId === t.id) opt.selected = true;\n          optgroup.appendChild(opt);\n        });\n\n        if (optgroup.children.length > 0) {\n          this.threatSelect.appendChild(optgroup);\n        }\n      });\n    } else {\n      threats.forEach(t => {\n        const opt = document.createElement('option');\n        opt.value = t.id;\n        const thTitle = this.app ? this.app.getNodeDisplayTitle(t) : (t.title || 'Threat');\n        opt.textContent = `${thTitle}: ${t.description || 'Untitled ' + thTitle}`;\n        if (this.threatId === t.id) opt.selected = true;\n        this.threatSelect.appendChild(opt);\n      });\n    }\n\n    if (this.threatId) {\n      this.threatSelect.value = this.threatId;\n    }\n  }\n\n  getNodeTitle(node, fallback = '') {\n    if (!node) return fallback;\n    if (this.app && typeof this.app.getNodeDisplayTitle === 'function') {\n      const dt = this.app.getNodeDisplayTitle(node);\n      if (dt) return dt;\n    }\n    return node.title || fallback;\n  }\n\n  getThreatTitle() {\n    const activeThreat = (this.app && this.app.getNodeById) ? this.app.getNodeById(this.threatId) : null;\n    return this.getNodeTitle(activeThreat, 'Threat');\n  }\n\n  setThreat(threatId) {\n    this.threatId = threatId;\n    if (this.isRunning) {\n      this.reset();\n    }\n    if (this.app.canvas && threatId) {\n      this.app.canvas.centerOnNode(threatId);\n      this.app.canvas.setActiveNode(threatId);\n    }\n    if (!this.isHandlingRemoteAction) {\n      this.broadcastSimAction('set_threat', { threat_id: threatId });\n    }\n  }\n\n  setMode(mode) {\n    this.mode = mode;\n    if (this.modeTag) {\n      this.modeTag.textContent = (mode === 'simulated') ? 'Simulated (30s)' : 'Real-Time';\n    }\n    if (this.isRunning) {\n      this.compilePlan(this.threatId);\n    }\n    if (!this.isHandlingRemoteAction) {\n      this.broadcastSimAction('set_mode', { mode: mode });\n    }\n  }\n\n  toggleHud() {\n    if (!this.hud) return;\n    const isClosed = this.hud.classList.contains('closed');\n    if (isClosed) {\n      this.openHud();\n    } else {\n      this.closeHud();\n    }\n  }\n\n  openHud() {\n    if (!this.hud) return;\n    this.hud.style.display = '';\n    this.hud.classList.remove('closed');\n    this.hud.classList.remove('collapsed');\n    const btnCollapse = document.getElementById('btnSimCollapse');\n    if (btnCollapse) {\n      btnCollapse.textContent = '−';\n      btnCollapse.title = 'Collapse Situation Runner';\n    }\n    this.populateThreats();\n    const btnToggle = document.getElementById('btnToggleSituationRunner');\n    if (btnToggle) btnToggle.classList.add('active');\n    requestAnimationFrame(() => this.reclampPosition());\n  }\n\n  closeHud() {\n    if (!this.hud) return;\n    this.hud.classList.add('closed');\n    this.hud.style.display = 'none';\n    const btnToggle = document.getElementById('btnToggleSituationRunner');\n    if (btnToggle) btnToggle.classList.remove('active');\n  }\n\n  toggleCollapse() {\n    if (!this.hud) return;\n    this.hud.classList.toggle('collapsed');\n    const isCollapsed = this.hud.classList.contains('collapsed');\n    const btnCollapse = document.getElementById('btnSimCollapse');\n    if (btnCollapse) {\n      btnCollapse.textContent = isCollapsed ? '+' : '−';\n      btnCollapse.title = isCollapsed ? 'Expand Situation Runner' : 'Collapse Situation Runner';\n    }\n    if (!isCollapsed) {\n      requestAnimationFrame(() => this.reclampPosition());\n    }\n  }\n\n  initDraggable() {\n    const header = document.getElementById('simHudHeader');\n    if (!header || !this.hud) return;\n\n    this._headerJustDragged = false;\n    let isPointerDown = false;\n    let isDragging = false;\n    let startX = 0;\n    let startY = 0;\n    let initialLeft = 0;\n    let initialTop = 0;\n\n    const onPointerDown = (e) => {\n      if (e.button !== undefined && e.button !== 0) return;\n      if (e.target.closest('.sim-hud-header-actions')) return;\n\n      isPointerDown = true;\n      isDragging = false;\n      this._headerJustDragged = false;\n      startX = e.clientX;\n      startY = e.clientY;\n\n      const parentEl = this.hud.offsetParent || document.body;\n      const parentRect = parentEl.getBoundingClientRect();\n      const hudRect = this.hud.getBoundingClientRect();\n\n      initialLeft = hudRect.left - parentRect.left;\n      initialTop = hudRect.top - parentRect.top;\n\n      try {\n        header.setPointerCapture(e.pointerId);\n      } catch (err) {}\n    };\n\n    const onPointerMove = (e) => {\n      if (!isPointerDown) return;\n\n      const dx = e.clientX - startX;\n      const dy = e.clientY - startY;\n\n      if (!isDragging) {\n        if (Math.hypot(dx, dy) > 4) {\n          isDragging = true;\n          this._headerJustDragged = true;\n          this.hud.classList.add('is-dragging');\n          this.hud.classList.add('is-moved');\n        }\n      }\n\n      if (isDragging) {\n        e.preventDefault();\n        const parentEl = this.hud.offsetParent || document.body;\n        const pRect = parentEl.getBoundingClientRect();\n        const curHudRect = this.hud.getBoundingClientRect();\n\n        const minLeft = 4;\n        const maxLeft = Math.max(minLeft, pRect.width - curHudRect.width - 4);\n        const minTop = 4;\n        const maxTop = Math.max(minTop, pRect.height - curHudRect.height - 4);\n\n        const targetLeft = Math.min(maxLeft, Math.max(minLeft, initialLeft + dx));\n        const targetTop = Math.min(maxTop, Math.max(minTop, initialTop + dy));\n\n        this.hud.style.left = `${Math.round(targetLeft)}px`;\n        this.hud.style.top = `${Math.round(targetTop)}px`;\n        this.hud.style.right = 'auto';\n        this.hud.style.bottom = 'auto';\n      }\n    };\n\n    const onPointerUp = (e) => {\n      if (!isPointerDown) return;\n      isPointerDown = false;\n\n      try {\n        if (header.hasPointerCapture && header.hasPointerCapture(e.pointerId)) {\n          header.releasePointerCapture(e.pointerId);\n        }\n      } catch (err) {}\n\n      if (isDragging) {\n        this.hud.classList.remove('is-dragging');\n        this.reclampPosition();\n        setTimeout(() => {\n          this._headerJustDragged = false;\n        }, 80);\n        isDragging = false;\n      }\n    };\n\n    const onPointerCancel = (e) => {\n      if (!isPointerDown) return;\n      isPointerDown = false;\n      try {\n        if (header.hasPointerCapture && header.hasPointerCapture(e.pointerId)) {\n          header.releasePointerCapture(e.pointerId);\n        }\n      } catch (err) {}\n      this.hud.classList.remove('is-dragging');\n      this.reclampPosition();\n      this._headerJustDragged = false;\n      isDragging = false;\n    };\n\n    header.addEventListener('pointerdown', onPointerDown);\n    header.addEventListener('pointermove', onPointerMove);\n    header.addEventListener('pointerup', onPointerUp);\n    header.addEventListener('pointercancel', onPointerCancel);\n\n    window.addEventListener('resize', () => {\n      this.reclampPosition();\n    });\n  }\n\n  reclampPosition() {\n    if (!this.hud || !this.hud.classList.contains('is-moved')) return;\n    if (this.hud.classList.contains('closed')) return;\n\n    const parentEl = this.hud.offsetParent || document.body;\n    const pRect = parentEl.getBoundingClientRect();\n    const curHudRect = this.hud.getBoundingClientRect();\n\n    if (pRect.width === 0 || pRect.height === 0) return;\n\n    const minLeft = 4;\n    const maxLeft = Math.max(minLeft, pRect.width - curHudRect.width - 4);\n    const minTop = 4;\n    const maxTop = Math.max(minTop, pRect.height - curHudRect.height - 4);\n\n    const currentLeft = curHudRect.left - pRect.left;\n    const currentTop = curHudRect.top - pRect.top;\n\n    const clampedLeft = Math.min(maxLeft, Math.max(minLeft, currentLeft));\n    const clampedTop = Math.min(maxTop, Math.max(minTop, currentTop));\n\n    this.hud.style.left = `${Math.round(clampedLeft)}px`;\n    this.hud.style.top = `${Math.round(clampedTop)}px`;\n    this.hud.style.right = 'auto';\n    this.hud.style.bottom = 'auto';\n  }\n\n  openWithThreat(threatId) {\n    this.threatId = threatId;\n    if (this.app.canvas && threatId) {\n      this.app.canvas.setActiveNode(threatId);\n    }\n    this.openHud();\n    if (this.threatSelect) this.threatSelect.value = threatId;\n    if (this.app.canvas && threatId) {\n      this.app.canvas.centerOnNode(threatId);\n    }\n  }\n\n  // Compile full Bowtie branch execution plan from Initiating Threat -> Barriers -> Top Event -> Mitigations\n  compilePlan(threatId) {\n    const nodes = this.app.workspace ? this.app.workspace.nodes : [];\n    const threat = nodes.find(n => n.id === threatId);\n    if (!threat) return [];\n\n    const diagNodes = nodes.filter(n => n.diagram_id === threat.diagram_id);\n    const barriers = this.app.layout ? this.app.layout.getLinearChainForThreat(threat, diagNodes) : [];\n    const topEvents = diagNodes.filter(n => n.node_type === 'top_event');\n    const topEvent = this.app.layout\n      ? this.app.layout.getTopEventForNode(threat, diagNodes)\n      : (topEvents.find(te => te.id === threat.parent_id) || topEvents[0] || null);\n\n    const steps = [];\n\n    // 1. Initial Threat activation step\n    const thTitle = this.getNodeTitle(threat, 'Threat');\n    steps.push({\n      type: 'threat_activation',\n      node: threat,\n      name: `${thTitle}: ${threat.description || threat.title || 'Initiating Event'}`,\n      durationReal: Math.max(0, this.scheduledDelaySec),\n    });\n\n    let prevNode = threat;\n\n    // 2. Linear Chain of Pre-Event Barriers\n    barriers.forEach((barrier, idx) => {\n      // Barrier timeframe parameters\n      const travelReal = (barrier.time_to_barrier_sec != null && barrier.time_to_barrier_sec > 0)\n        ? barrier.time_to_barrier_sec\n        : 5.0; // default 5s\n      const delayReal = (barrier.delay_to_activate_sec != null && barrier.delay_to_activate_sec >= 0)\n        ? barrier.delay_to_activate_sec\n        : 2.0; // default 2s\n      const lifeReal = (barrier.barrier_life_sec != null && barrier.barrier_life_sec > 0)\n        ? Math.min(barrier.barrier_life_sec, 60.0)\n        : 15.0; // default 15s\n\n      // Find Escalation Factors & Mitigations\n      const efs = diagNodes.filter(n => n.parent_id === barrier.id && n.node_type === 'escalation_factor');\n      const efDetails = efs.map(ef => {\n        const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n        return { ef, mits };\n      });\n\n      // Step: Travel along line to Barrier\n      steps.push({\n        type: 'travel_to_barrier',\n        fromNode: prevNode,\n        toNode: barrier,\n        barrierIdx: idx + 1,\n        totalBarriers: barriers.length,\n        durationReal: travelReal,\n      });\n\n      // Step: Barrier Impact pulse\n      steps.push({\n        type: 'barrier_impact',\n        node: barrier,\n        barrierIdx: idx + 1,\n        durationReal: 0.6,\n      });\n\n      // Step: Barrier Holding & Delay test\n      steps.push({\n        type: 'barrier_holding',\n        node: barrier,\n        barrierIdx: idx + 1,\n        durationReal: lifeReal,\n        delayReal: delayReal,\n        efDetails: efDetails,\n      });\n\n      // Step: Resolution (breach or contain)\n      // If last barrier has criticality \"Safety Critical\" or high effectiveness, user can configure containment,\n      // otherwise it breaches and advances downstream to showcase full Bowtie propagation.\n      const isLastBarrier = (idx === barriers.length - 1);\n      const isContainment = (barrier.barrier_life_sec != null && barrier.barrier_life_sec >= 999);\n\n      steps.push({\n        type: 'barrier_resolution',\n        node: barrier,\n        isContainment: isContainment,\n        isLastBarrier: isLastBarrier,\n        durationReal: 0.8,\n      });\n\n      prevNode = barrier;\n    });\n\n    // 3. Propagation to Top Event (if not contained)\n    if (topEvent) {\n      steps.push({\n        type: 'travel_to_top_event',\n        fromNode: prevNode,\n        toNode: topEvent,\n        durationReal: 4.0,\n      });\n\n      steps.push({\n        type: 'top_event_loss',\n        node: topEvent,\n        durationReal: 3.5,\n      });\n\n      // 4. Post-Event Right Wing: Concurrent Outcome Branches for this Top Event cluster\n      const ownership = (this.app.layout && topEvents.length > 0)\n        ? this.app.layout.buildClusterOwnership(diagNodes, topEvents)\n        : null;\n      let branchDefs = (this.app.layout && ownership)\n        ? this.app.layout.getClusterRightBranches(topEvent, diagNodes, ownership)\n        : [];\n\n      if (branchDefs.length === 0) {\n        const rightRoots = diagNodes.filter(n => n.parent_id === topEvent.id);\n        branchDefs = rightRoots.map(root => ({\n          root,\n          chain: this.app.layout ? this.app.layout.getLinearChainForOutcome(root, diagNodes) : [root],\n        }));\n      }\n\n      if (branchDefs.length > 0) {\n        const branches = branchDefs.map((bDef, bIdx) => {\n          const root = bDef.root;\n          const chain = (bDef.chain && bDef.chain.length > 0)\n            ? bDef.chain\n            : (this.app.layout ? this.app.layout.getLinearChainForOutcome(root, diagNodes) : [root]);\n          const legs = [];\n          let pNode = topEvent;\n          let branchTotalReal = 0;\n\n          chain.forEach((cNode, cIdx) => {\n            const travelR = (cNode.time_to_barrier_sec != null && cNode.time_to_barrier_sec > 0)\n              ? cNode.time_to_barrier_sec\n              : 3.0;\n            const lifeR = (cNode.barrier_life_sec != null && cNode.barrier_life_sec > 0)\n              ? Math.min(cNode.barrier_life_sec, 30.0)\n              : 8.0;\n            const isBarrier = (cNode.node_type === 'outcome_barrier');\n\n            let efDetails = [];\n            if (isBarrier) {\n              const efs = diagNodes.filter(n => n.parent_id === cNode.id && n.node_type === 'escalation_factor');\n              efDetails = efs.map(ef => {\n                const mits = diagNodes.filter(n => n.parent_id === ef.id && n.node_type === 'mitigation');\n                return { ef, mits };\n              });\n            }\n\n            legs.push({\n              legIdx: cIdx,\n              fromNode: pNode,\n              toNode: cNode,\n              travelReal: travelR,\n              lifeReal: isBarrier ? lifeR : 2.0,\n              isBarrier: isBarrier,\n              efDetails: efDetails,\n            });\n            branchTotalReal += travelR + (isBarrier ? lifeR : 2.0);\n            pNode = cNode;\n          });\n\n          return {\n            branchIdx: bIdx,\n            root: root,\n            chain: chain,\n            legs: legs,\n            totalReal: branchTotalReal,\n            particle: null,\n            pathEl: null,\n            pathLen: 0,\n            isReverse: false,\n          };\n        });\n\n        const maxBranchReal = Math.max(...branches.map(b => b.totalReal), 10.0);\n        steps.push({\n          type: 'concurrent_outcome_branches',\n          name: `Concurrent Consequence Propagation (${branches.length} branches)`,\n          topEvent: topEvent,\n          branches: branches,\n          durationReal: maxBranchReal,\n        });\n      }\n    }\n\n    // Compute total real duration across all steps\n    const totalReal = steps.reduce((sum, s) => sum + (s.durationReal || 1.0), 0);\n    this.totalPlanRealSec = Math.max(1.0, totalReal);\n\n    // In 30s Simulated Mode: scale durations strictly proportionally!\n    // Total simulated presentation = ~30.0s (or slightly adjusted by branch complexity)\n    this.targetSimDuration = Math.min(35.0, Math.max(25.0, barriers.length * 7.0 + 8.0));\n    this.scaleRatio = (this.mode === 'simulated')\n      ? (this.targetSimDuration / this.totalPlanRealSec)\n      : 1.0;\n\n    steps.forEach(s => {\n      s.durationSim = (this.mode === 'simulated')\n        ? Math.max(0.4, (s.durationReal || 1.0) * this.scaleRatio)\n        : (s.durationReal || 1.0);\n\n      // Precalculate scheduled events for concurrent branches\n      if (s.type === 'concurrent_outcome_branches' && s.branches) {\n        s.branches.forEach(branch => {\n          let curTimeSim = 0;\n          branch.events = [];\n          branch.legs.forEach(leg => {\n            const travelSim = (this.mode === 'simulated')\n              ? Math.max(0.4, leg.travelReal * this.scaleRatio)\n              : (leg.travelReal / this.speed);\n            const lifeSim = (this.mode === 'simulated')\n              ? Math.max(0.4, leg.lifeReal * this.scaleRatio)\n              : (leg.lifeReal / this.speed);\n\n            const travelEvt = {\n              type: 'travel',\n              legIdx: leg.legIdx,\n              fromNode: leg.fromNode,\n              toNode: leg.toNode,\n              travelReal: leg.travelReal,\n              startSim: curTimeSim,\n              endSim: curTimeSim + travelSim,\n              isBarrier: leg.isBarrier,\n            };\n            branch.events.push(travelEvt);\n            curTimeSim += travelSim;\n\n            const holdEvt = {\n              type: leg.isBarrier ? 'holding' : 'outcome',\n              legIdx: leg.legIdx,\n              node: leg.toNode,\n              lifeReal: leg.lifeReal,\n              startSim: curTimeSim,\n              endSim: curTimeSim + lifeSim,\n              isBarrier: leg.isBarrier,\n              efDetails: leg.efDetails || [],\n            };\n            branch.events.push(holdEvt);\n            curTimeSim += lifeSim;\n          });\n          branch.totalSim = curTimeSim;\n        });\n\n        s.durationSim = Math.max(...s.branches.map(b => b.totalSim), 4.0);\n      }\n    });\n\n    this.plan = steps;\n    return steps;\n  }\n\n  getPastElapsedSec() {\n    if (this.mode !== 'realtime') return 0;\n    const btnSchedPast = document.getElementById('btnSchedPast');\n    if (!btnSchedPast || !btnSchedPast.classList.contains('active')) return 0;\n\n    const h = parseFloat(document.getElementById('simElapsedHours')?.value || '0') || 0;\n    const m = parseFloat(document.getElementById('simElapsedMins')?.value || '0') || 0;\n    return Math.max(0, (h * 3600) + (m * 60));\n  }\n\n  run() {\n    if (!this.threatId) {\n      this.populateThreats();\n      if (!this.threatId) {\n        alert(\"Please create or select a Threat on the canvas to run the Situation Runner.\");\n        return;\n      }\n    }\n\n    this.compilePlan(this.threatId);\n    if (this.plan.length === 0) {\n      alert(\"No branch found for the selected Threat.\");\n      return;\n    }\n\n    this.isRunning = true;\n    this.isPaused = false;\n    this.updateTransportUI('running');\n    this.clearVisualStates();\n\n    // Trigger visual Threat active pulsing\n    const threatEl = document.getElementById(`node-${this.threatId}`);\n    if (threatEl) {\n      threatEl.classList.add('is-sim-active-threat');\n      this.setNodeBadge(threatEl, '⚡ ACTIVE THREAT', 'badge-threat-active');\n    }\n\n    const pastElapsedSec = this.getPastElapsedSec();\n    if (pastElapsedSec > 0) {\n      this.elapsedRealSec = pastElapsedSec;\n      this.elapsedSimSec = pastElapsedSec;\n      this.fastForwardToElapsed(pastElapsedSec);\n    } else {\n      this.currentStepIdx = 0;\n      this.stepProgress = 0.0;\n      this.elapsedSimSec = 0.0;\n      this.elapsedRealSec = 0.0;\n      const startNode = this.plan[0].node;\n      const startTitle = this.getNodeTitle(startNode, 'Threat');\n      this.logEvent(`[00:00.0] ⚡ Initiating Situation Runner for ${startTitle}: \"${startNode.description || startTitle}\"`, 'threat');\n      if (this.plan.length > 0) {\n        this.enterStep(this.plan[0]);\n      }\n    }\n\n    this.lastTimestamp = performance.now();\n    this.animFrameId = requestAnimationFrame((ts) => this.loop(ts));\n\n    if (!this.isHandlingRemoteAction) {\n      this.broadcastSimAction('run', {\n        threat_id: this.threatId,\n        mode: this.mode,\n        speed: this.speed,\n        scheduled_delay: this.scheduledDelaySec,\n        elapsed_sim_sec: this.elapsedSimSec,\n        step_idx: this.currentStepIdx,\n        step_progress: this.stepProgress,\n      });\n    }\n  }\n\n  fastForwardToElapsed(elapsedSec) {\n    let tAccum = 0;\n    let matchedStep = null;\n\n    for (let i = 0; i < this.plan.length; i++) {\n      const step = this.plan[i];\n      const stepReal = (step.durationReal || 1.0);\n      const stepStart = tAccum;\n      const stepEnd = tAccum + stepReal;\n\n      if (elapsedSec >= stepEnd) {\n        this.applyHistoricalStepCompletion(step, stepEnd);\n        tAccum = stepEnd;\n      } else {\n        matchedStep = step;\n        this.currentStepIdx = i;\n        const inStep = elapsedSec - stepStart;\n        this.stepProgress = Math.min(0.99, inStep / stepReal);\n        this.enterStep(step);\n        const thName = this.getThreatTitle();\n        this.logEvent(`[${this.formatTime(elapsedSec)}] 🕒 Joined ongoing incident: ${thName} activated ${Math.floor(elapsedSec / 60)}m ago. Resuming live monitoring!`, 'threat');\n        break;\n      }\n    }\n\n    if (!matchedStep) {\n      this.currentStepIdx = this.plan.length - 1;\n      this.stepProgress = 1.0;\n      this.completeSimulation(false);\n      const thName = this.getThreatTitle();\n      this.logEvent(`[${this.formatTime(elapsedSec)}] 💥 ${thName} already breached all defenses ${Math.floor(elapsedSec / 60)}m ago.`, 'breached');\n    }\n  }\n\n  applyHistoricalStepCompletion(step, timeSec) {\n    const timeStr = this.formatTime(timeSec);\n    if (step.type === 'barrier_holding' || step.type === 'barrier_resolution') {\n      const b = step.node;\n      const bEl = document.getElementById(`node-${b.id}`);\n      if (bEl) {\n        bEl.classList.remove('is-sim-holding', 'is-sim-challenged', 'is-sim-impacted');\n        bEl.classList.add('is-sim-breached');\n        this.setNodeBadge(bEl, '💥 BREACHED', 'badge-breached');\n      }\n      const bTitle = this.getNodeTitle(b, 'Barrier');\n      this.logEvent(`[${timeStr}] 💥 ${bTitle} Breached in the past: \"${b.description || bTitle}\"`, 'breached');\n    } else if (step.type === 'top_event_breach') {\n      const te = step.node;\n      const teEl = document.getElementById(`node-${te.id}`);\n      if (teEl) {\n        teEl.classList.add('is-sim-breached');\n        this.setNodeBadge(teEl, '🚨 TOP EVENT BREACHED', 'badge-breached');\n      }\n      const teTitle = this.getNodeTitle(te, 'Top Event');\n      this.logEvent(`[${timeStr}] 🚨 Loss of Containment ${teTitle} breached in the past.`, 'top-event');\n    }\n  }\n\n  pause() {\n    if (!this.isRunning || this.isPaused) return;\n    this.isPaused = true;\n    if (this.animFrameId) {\n      cancelAnimationFrame(this.animFrameId);\n      this.animFrameId = null;\n    }\n    this.updateTransportUI('paused');\n    this.logEvent(`[${this.formatTime(this.elapsedSimSec)}] ⏸️ Situation Runner paused.`, 'info');\n    if (!this.isHandlingRemoteAction) {\n      this.broadcastSimAction('pause', {\n        threat_id: this.threatId,\n        step_idx: this.currentStepIdx,\n        elapsed_sim_sec: this.elapsedSimSec,\n        step_progress: this.stepProgress,\n      });\n    }\n  }\n\n  resume() {\n    if (!this.isRunning || !this.isPaused) return;\n    this.isPaused = false;\n    this.updateTransportUI('running');\n    this.logEvent(`[${this.formatTime(this.elapsedSimSec)}] ▶️ Situation Runner resumed.`, 'info');\n    this.lastTimestamp = performance.now();\n    this.animFrameId = requestAnimationFrame((ts) => this.loop(ts));\n    if (!this.isHandlingRemoteAction) {\n      this.broadcastSimAction('resume', {\n        threat_id: this.threatId,\n        step_idx: this.currentStepIdx,\n        elapsed_sim_sec: this.elapsedSimSec,\n        step_progress: this.stepProgress,\n      });\n    }\n  }\n\n  reset() {\n    this.isRunning = false;\n    this.isPaused = false;\n    if (this.animFrameId) {\n      cancelAnimationFrame(this.animFrameId);\n      this.animFrameId = null;\n    }\n    this.currentStepIdx = 0;\n    this.stepProgress = 0.0;\n    this.elapsedSimSec = 0.0;\n    this.elapsedRealSec = 0.0;\n\n    this.removeAllParticles();\n    this.clearVisualStates();\n    this.updateTransportUI('idle');\n    this.updateClocks(0, 0);\n    this.updateProgressBar(0);\n    if (this.metricStage) this.metricStage.textContent = 'Ready';\n    if (!this.isHandlingRemoteAction) {\n      this.broadcastSimAction('reset');\n    }\n  }\n\n  step() {\n    if (!this.isRunning) {\n      this.run();\n      this.pause();\n      return;\n    }\n    // Advance directly to completion of current step\n    this.stepProgress = 1.0;\n    this.advanceStep();\n    if (!this.isHandlingRemoteAction) {\n      this.broadcastSimAction('step', {\n        threat_id: this.threatId,\n        step_idx: this.currentStepIdx,\n        elapsed_sim_sec: this.elapsedSimSec,\n      });\n    }\n  }\n\n  // Main animation / simulation tick loop\n  loop(timestamp) {\n    if (!this.isRunning || this.isPaused) return;\n\n    const deltaMs = Math.min(100, timestamp - (this.lastTimestamp || timestamp));\n    this.lastTimestamp = timestamp;\n\n    const currentStep = this.plan[this.currentStepIdx];\n    if (!currentStep) {\n      this.completeSimulation();\n      return;\n    }\n\n    // Step duration in seconds\n    const stepDurationSec = (this.mode === 'simulated')\n      ? currentStep.durationSim\n      : (currentStep.durationReal / this.speed);\n\n    // Update progress fraction\n    if (stepDurationSec > 0) {\n      this.stepProgress += (deltaMs / 1000) / stepDurationSec;\n    } else {\n      this.stepProgress = 1.0;\n    }\n\n    // Update global clocks\n    const dtSim = (this.mode === 'simulated') ? (deltaMs / 1000) : (deltaMs / 1000) * this.scaleRatio;\n    const dtReal = (deltaMs / 1000) * (this.mode === 'realtime' ? this.speed : (1.0 / this.scaleRatio));\n    this.elapsedSimSec += dtSim;\n    this.elapsedRealSec += dtReal;\n\n    this.updateClocks(this.elapsedSimSec, this.elapsedRealSec);\n\n    // Update branch progress bar\n    const totalSteps = this.plan.length;\n    const overallFraction = Math.min(1.0, (this.currentStepIdx + Math.min(1.0, this.stepProgress)) / totalSteps);\n    this.updateProgressBar(overallFraction);\n\n    // Render active step animation (particle glide, holding countdown, etc.)\n    this.renderCurrentStep(currentStep, this.stepProgress);\n\n    // Periodic synchronization broadcast (~1.5s) to keep all remote collaborators in lockstep\n    const nowTime = performance.now();\n    if (!this.isHandlingRemoteAction && (!this.lastSyncBroadcast || (nowTime - this.lastSyncBroadcast > 1500))) {\n      this.lastSyncBroadcast = nowTime;\n      this.broadcastSimAction('sync', {\n        threat_id: this.threatId,\n        mode: this.mode,\n        speed: this.speed,\n        step_idx: this.currentStepIdx,\n        elapsed_sim_sec: this.elapsedSimSec,\n        step_progress: this.stepProgress,\n      });\n    }\n\n    // Check step completion\n    if (this.stepProgress >= 1.0) {\n      this.advanceStep();\n    }\n\n    if (this.isRunning && !this.isPaused) {\n      this.animFrameId = requestAnimationFrame((ts) => this.loop(ts));\n    }\n  }\n\n  // Advance to next step in execution plan\n  advanceStep() {\n    this.currentStepIdx++;\n    this.stepProgress = 0.0;\n\n    // Remove all particles when wire traversal completes\n    this.removeAllParticles();\n\n    if (this.currentStepIdx >= this.plan.length) {\n      this.completeSimulation();\n      return;\n    }\n\n    // Initialize the newly entered step\n    const nextStep = this.plan[this.currentStepIdx];\n    this.enterStep(nextStep);\n  }\n\n  enterStep(step) {\n    const timeStr = this.formatTime(this.elapsedSimSec);\n\n    switch (step.type) {\n      case 'travel_to_barrier': {\n        const b = step.toNode;\n        const bTitle = this.getNodeTitle(b, 'Barrier');\n        const thName = this.getThreatTitle();\n        const bEl = document.getElementById(`node-${b.id}`);\n        if (bEl) {\n          bEl.classList.add('is-sim-challenged');\n          this.setNodeBadge(bEl, `⚡ INBOUND (${step.durationReal.toFixed(1)}s)`, 'badge-inbound');\n        }\n        if (this.metricStage) this.metricStage.textContent = `Traveling to ${bTitle} ${step.barrierIdx}: \"${b.description || bTitle}\"`;\n        this.logEvent(`[${timeStr}] ⚡ ${thName} energy traveling along line to ${bTitle} #${step.barrierIdx}: \"${b.description || bTitle}\" (travel time: ${step.durationReal.toFixed(1)}s)`, 'travel');\n        this.initParticleGlide(step.fromNode.id, step.toNode.id);\n        break;\n      }\n\n      case 'barrier_impact': {\n        const b = step.node;\n        const bTitle = this.getNodeTitle(b, 'Barrier');\n        const thName = this.getThreatTitle();\n        const bEl = document.getElementById(`node-${b.id}`);\n        if (bEl) {\n          bEl.classList.remove('is-sim-challenged');\n          bEl.classList.add('is-sim-impacted');\n          this.triggerShockwave(b);\n        }\n        if (this.metricStage) this.metricStage.textContent = `Impacted ${bTitle}: \"${b.description || bTitle}\"!`;\n        this.logEvent(`[${timeStr}] 💥 ${bTitle} impact pulse: ${thName} challenged ${bTitle.toLowerCase()} \"${b.description || bTitle}\"!`, 'impact');\n        break;\n      }\n\n      case 'barrier_holding': {\n        const b = step.node;\n        const bTitle = this.getNodeTitle(b, 'Barrier');\n        const thName = this.getThreatTitle();\n        const bEl = document.getElementById(`node-${b.id}`);\n        if (bEl) {\n          bEl.classList.remove('is-sim-impacted');\n          bEl.classList.add('is-sim-holding');\n        }\n        if (this.metricStage) this.metricStage.textContent = `${bTitle} Holding: \"${b.description || bTitle}\" (Life: ${step.durationReal.toFixed(0)}s)`;\n        this.logEvent(`[${timeStr}] 🛡️ ${bTitle} active and resisting ${thName.toLowerCase()}. Rated life: ${step.durationReal.toFixed(0)}s (activation delay: ${step.delayReal.toFixed(1)}s)`, 'holding');\n\n        // Trigger Escalation Factors & Mitigations if attached\n        if (step.efDetails && step.efDetails.length > 0) {\n          step.efDetails.forEach(({ ef, mits }) => {\n            const efTitle = this.getNodeTitle(ef, 'Escalation Factor');\n            const efEl = document.getElementById(`node-${ef.id}`);\n            if (efEl) {\n              efEl.classList.remove('is-sim-challenged');\n              efEl.classList.add('is-sim-escalation-pulsing');\n              this.setNodeBadge(efEl, '⚠️ ESCALATING', 'badge-escalating');\n            }\n            this.logEvent(`[${timeStr}] ⚠️ ${efTitle} active: \"${ef.description || efTitle}\" challenges ${bTitle.toLowerCase()} stability!`, 'impact');\n\n            mits.forEach(m => {\n              const mEl = document.getElementById(`node-${m.id}`);\n              if (mEl) {\n                mEl.classList.remove('is-sim-contained');\n                mEl.classList.add('is-sim-mitigation-active');\n                this.setNodeBadge(mEl, '🛠️ ENGAGED', 'badge-mitigation-active');\n              }\n            });\n          });\n        }\n        break;\n      }\n\n      case 'barrier_resolution': {\n        const b = step.node;\n        const bTitle = this.getNodeTitle(b, 'Barrier');\n        const thName = this.getThreatTitle();\n        const bEl = document.getElementById(`node-${b.id}`);\n        if (step.isContainment) {\n          if (bEl) {\n            bEl.classList.remove('is-sim-holding');\n            bEl.classList.add('is-sim-contained');\n            this.setNodeBadge(bEl, `✅ ${thName.toUpperCase()} ARRESTED`, 'badge-contained');\n          }\n          this.logEvent(`[${timeStr}] ✅ SAFE: ${bTitle} \"${b.description || bTitle}\" held successfully! Scenario contained.`, 'contained');\n          this.completeSimulation(true);\n        } else {\n          if (bEl) {\n            bEl.classList.remove('is-sim-holding');\n            bEl.classList.add('is-sim-breached');\n            this.setNodeBadge(bEl, '💥 BREACHED', 'badge-breached');\n          }\n          this.logEvent(`[${timeStr}] 💥 BREACH: ${bTitle} life depleted. ${bTitle} \"${b.description || bTitle}\" breached downstream!`, 'breached');\n        }\n        break;\n      }\n\n      case 'travel_to_top_event': {\n        const te = step.toNode;\n        const teTitle = this.getNodeTitle(te, 'Top Event');\n        const thName = this.getThreatTitle();\n        if (this.metricStage) this.metricStage.textContent = `Breach energy advancing to ${teTitle}: \"${te.description || teTitle}\"`;\n        this.logEvent(`[${timeStr}] ⚡ ${thName} wave breaches barrier defenses and advances along trunk to ${teTitle}!`, 'travel');\n        this.initParticleGlide(step.fromNode.id, step.toNode.id);\n        break;\n      }\n\n      case 'top_event_loss': {\n        const te = step.node;\n        const teTitle = this.getNodeTitle(te, 'Top Event');\n        const teEl = document.getElementById(`node-${te.id}`);\n        if (teEl) {\n          teEl.classList.add('is-sim-topevent-alarm');\n          this.setNodeBadge(teEl, '🚨 LOSS OF CONTAINMENT', 'badge-topevent-breached');\n          this.triggerShockwave(te);\n        }\n        if (this.metricStage) this.metricStage.textContent = `🚨 ${teTitle.toUpperCase()} OCCURRED: ${te.description || teTitle}!`;\n        this.logEvent(`[${timeStr}] 🚨 CATASTROPHIC EVENT: ${teTitle} triggered — \"${te.description || teTitle}\"! All pre-event barriers defeated.`, 'top-event');\n        break;\n      }\n\n      case 'concurrent_outcome_branches': {\n        if (this.metricStage) {\n          this.metricStage.textContent = `Breach energy propagating across ${step.branches.length} outcome branches concurrently...`;\n        }\n        this.logEvent(`[${timeStr}] ⚡ Breach wave splits at Top Event into ${step.branches.length} concurrent outcome branches!`, 'travel');\n\n        step.branches.forEach(branch => {\n          branch.shockwaveTriggered = new Set();\n          const firstLeg = branch.legs[0];\n          if (firstLeg) {\n            const targetNode = firstLeg.toNode;\n            const targetTitle = this.getNodeTitle(targetNode, 'Branch Target');\n            const targetEl = document.getElementById(`node-${targetNode.id}`);\n            if (targetEl) {\n              targetEl.classList.add('is-sim-challenged');\n              this.setNodeBadge(targetEl, `⚡ INBOUND (${firstLeg.travelReal.toFixed(1)}s)`, 'badge-inbound');\n            }\n            this.logEvent(`[${timeStr}] ⚡ Branch #${branch.branchIdx + 1}: ball launched toward \"${targetNode.description || targetTitle}\" (travel time: ${firstLeg.travelReal.toFixed(1)}s)`, 'travel');\n            this.spawnBranchParticle(branch, firstLeg.fromNode.id, firstLeg.toNode.id);\n            branch.currentFromId = firstLeg.fromNode.id;\n            branch.currentToId = firstLeg.toNode.id;\n          }\n        });\n        break;\n      }\n\n      case 'travel_to_outcome': {\n        const oc = step.toNode;\n        const ocTitle = this.getNodeTitle(oc, 'Outcome');\n        this.initParticleGlide(step.fromNode.id, step.toNode.id);\n        this.logEvent(`[${timeStr}] ⚡ Post-event consequence propagating to \"${oc.description || ocTitle}\"...`, 'travel');\n        break;\n      }\n\n      case 'mitigation_holding': {\n        const mit = step.node;\n        const mitTitle = this.getNodeTitle(mit, 'Mitigation');\n        const mitEl = document.getElementById(`node-${mit.id}`);\n        if (mitEl) {\n          mitEl.classList.remove('is-sim-challenged');\n          mitEl.classList.add('is-sim-outcome-holding');\n          this.setNodeBadge(mitEl, '🛡️ ACTIVE MITIGATION', 'badge-outcome-holding');\n        }\n        this.logEvent(`[${timeStr}] 🛠️ Post-event ${mitTitle.toLowerCase()} activated: \"${mit.description || mitTitle}\" mitigating outcome severity.`, 'holding');\n        break;\n      }\n\n      case 'outcome_impact': {\n        const oc = step.node;\n        const ocTitle = this.getNodeTitle(oc, 'Outcome');\n        const ocEl = document.getElementById(`node-${oc.id}`);\n        if (ocEl) {\n          ocEl.classList.add('is-sim-breached');\n          this.setNodeBadge(ocEl, '🔥 FINAL OUTCOME', 'badge-breached');\n        }\n        this.logEvent(`[${timeStr}] 💥 ${ocTitle} realized: \"${oc.description || ocTitle}\".`, 'breached');\n        break;\n      }\n    }\n  }\n\n  // Dynamic frame-by-frame updates for the active step\n  renderCurrentStep(step, fraction) {\n    const f = Math.min(1.0, Math.max(0.0, fraction));\n\n    // Update single particle position along SVG connection path with direction verification\n    if (this.activeParticle && this.activeParticlePath && this.activeParticleLen > 0) {\n      try {\n        const effF = this.activeParticleReverse ? (1.0 - f) : f;\n        const dist = effF * this.activeParticleLen;\n        const pt = this.activeParticlePath.getPointAtLength(dist);\n        this.activeParticle.setAttribute('transform', `translate(${pt.x}, ${pt.y})`);\n      } catch (e) {\n        // SVG length exception fallback\n      }\n    }\n\n    // Live countdown update on holding barrier card\n    if (step.type === 'barrier_holding') {\n      const bEl = document.getElementById(`node-${step.node.id}`);\n      if (bEl) {\n        const remainingRealSec = Math.max(0, step.durationReal * (1.0 - f));\n        this.setNodeBadge(bEl, `🛡️ HOLDING (${remainingRealSec.toFixed(0)}s)`, 'badge-holding');\n      }\n    }\n\n    // Concurrent multi-branch rendering for outcome branches\n    if (step.type === 'concurrent_outcome_branches' && step.branches) {\n      const stepDurationSec = (this.mode === 'simulated')\n        ? step.durationSim\n        : (step.durationReal / this.speed);\n      const curTimeSim = f * stepDurationSec;\n\n      step.branches.forEach(branch => {\n        if (!branch.events || branch.events.length === 0) return;\n\n        let activeEvt = branch.events.find(e => curTimeSim >= e.startSim && curTimeSim < e.endSim);\n        if (!activeEvt && curTimeSim >= branch.totalSim) {\n          activeEvt = branch.events[branch.events.length - 1]; // completed\n        }\n\n        if (activeEvt) {\n          if (activeEvt.type === 'travel') {\n            const evtDur = activeEvt.endSim - activeEvt.startSim;\n            const evtF = (evtDur > 0) ? Math.min(1.0, Math.max(0.0, (curTimeSim - activeEvt.startSim) / evtDur)) : 1.0;\n\n            if (!branch.particle || branch.currentFromId !== activeEvt.fromNode.id || branch.currentToId !== activeEvt.toNode.id) {\n              this.spawnBranchParticle(branch, activeEvt.fromNode.id, activeEvt.toNode.id);\n              branch.currentFromId = activeEvt.fromNode.id;\n              branch.currentToId = activeEvt.toNode.id;\n            }\n\n            this.updateBranchParticlePosition(branch, evtF);\n\n            // If the fromNode was an outcome barrier, it has now breached downstream\n            if (activeEvt.fromNode && activeEvt.fromNode.node_type === 'outcome_barrier') {\n              const fromEl = document.getElementById(`node-${activeEvt.fromNode.id}`);\n              if (fromEl) {\n                fromEl.classList.remove('is-sim-outcome-holding', 'is-sim-contained', 'is-sim-challenged');\n                fromEl.classList.add('is-sim-breached');\n                this.setNodeBadge(fromEl, '💥 BARRIER BREACHED', 'badge-breached');\n              }\n            }\n\n            const toEl = document.getElementById(`node-${activeEvt.toNode.id}`);\n            if (toEl) {\n              toEl.classList.add('is-sim-challenged');\n              const remainingTravel = Math.max(0, activeEvt.travelReal * (1.0 - evtF));\n              this.setNodeBadge(toEl, `⚡ INBOUND (${remainingTravel.toFixed(1)}s)`, 'badge-inbound');\n            }\n          } else if (activeEvt.type === 'holding') {\n            this.removeBranchParticle(branch);\n\n            const bNode = activeEvt.node;\n            const bEl = document.getElementById(`node-${bNode.id}`);\n            if (bEl) {\n              bEl.classList.remove('is-sim-challenged', 'is-sim-contained');\n              bEl.classList.add('is-sim-outcome-holding');\n\n              const evtDur = activeEvt.endSim - activeEvt.startSim;\n              const evtF = (evtDur > 0) ? Math.min(1.0, Math.max(0.0, (curTimeSim - activeEvt.startSim) / evtDur)) : 1.0;\n              const remainingLife = Math.max(0, activeEvt.lifeReal * (1.0 - evtF));\n              this.setNodeBadge(bEl, `🛡️ BARRIER HOLDING (${remainingLife.toFixed(0)}s)`, 'badge-outcome-holding');\n            }\n\n            // Animate attached Escalation Factors & Mitigations\n            if (activeEvt.efDetails && activeEvt.efDetails.length > 0) {\n              activeEvt.efDetails.forEach(({ ef, mits }) => {\n                const efEl = document.getElementById(`node-${ef.id}`);\n                if (efEl) {\n                  efEl.classList.remove('is-sim-challenged');\n                  efEl.classList.add('is-sim-escalation-pulsing');\n                  this.setNodeBadge(efEl, '⚠️ ESCALATING', 'badge-escalating');\n                }\n                mits.forEach(m => {\n                  const mEl = document.getElementById(`node-${m.id}`);\n                  if (mEl) {\n                    mEl.classList.remove('is-sim-contained');\n                    mEl.classList.add('is-sim-mitigation-active');\n                    this.setNodeBadge(mEl, '🛠️ ENGAGED', 'badge-mitigation-active');\n                  }\n                });\n              });\n            }\n\n            if (!branch.shockwaveTriggered.has(bNode.id)) {\n              branch.shockwaveTriggered.add(bNode.id);\n              this.triggerShockwave(bNode);\n              const bTitle = this.getNodeTitle(bNode, 'Outcome Barrier');\n              this.logEvent(`[${this.formatTime(this.elapsedSimSec)}] 🛡️ ${bTitle} holding on Branch #${branch.branchIdx + 1}: \"${bNode.description || bTitle}\".`, 'holding');\n            }\n          } else if (activeEvt.type === 'outcome') {\n            this.removeBranchParticle(branch);\n\n            // Mark any previous barriers on this branch as breached\n            if (branch.chain && branch.chain.length > 0) {\n              branch.chain.forEach(prevNode => {\n                if (prevNode.node_type === 'outcome_barrier' && prevNode.id !== activeEvt.node.id) {\n                  const prevEl = document.getElementById(`node-${prevNode.id}`);\n                  if (prevEl) {\n                    prevEl.classList.remove('is-sim-outcome-holding', 'is-sim-contained', 'is-sim-challenged');\n                    prevEl.classList.add('is-sim-breached');\n                    this.setNodeBadge(prevEl, '💥 BARRIER BREACHED', 'badge-breached');\n                  }\n                }\n              });\n            }\n\n            const ocNode = activeEvt.node;\n            const ocEl = document.getElementById(`node-${ocNode.id}`);\n            if (ocEl) {\n              ocEl.classList.remove('is-sim-challenged');\n              ocEl.classList.add('is-sim-breached');\n              this.setNodeBadge(ocEl, '🔥 FINAL OUTCOME', 'badge-breached');\n            }\n\n            if (!branch.shockwaveTriggered.has(ocNode.id)) {\n              branch.shockwaveTriggered.add(ocNode.id);\n              this.triggerShockwave(ocNode);\n              const ocTitle = this.getNodeTitle(ocNode, 'Consequence');\n              this.logEvent(`[${this.formatTime(this.elapsedSimSec)}] 💥 ${ocTitle} realized on Branch #${branch.branchIdx + 1}: \"${ocNode.description || ocTitle}\".`, 'breached');\n            }\n          }\n        } else {\n          this.removeBranchParticle(branch);\n        }\n      });\n    }\n  }\n\n  // Create glowing traveling particle in SVG simulationLayer\n  initParticleGlide(fromNodeId, toNodeId) {\n    this.removeParticle();\n    const simLayer = this.app.canvas ? this.app.canvas.simulationLayer : document.getElementById('simulationLayer');\n    if (!simLayer) return;\n\n    // Find the exact SVG <path> connecting fromNode to toNode\n    const pathEl = this.app.canvas ? this.app.canvas.findConnectionPath(fromNodeId, toNodeId) : null;\n    if (!pathEl) {\n      return;\n    }\n\n    try {\n      this.activeParticleLen = pathEl.getTotalLength();\n      this.activeParticlePath = pathEl;\n\n      const p0 = pathEl.getPointAtLength(0);\n      const p1 = pathEl.getPointAtLength(this.activeParticleLen);\n      const fromNode = this.app.workspace ? this.app.workspace.nodes.find(n => n.id === fromNodeId) : null;\n      let isReverse = false;\n      if (fromNode) {\n        const fromCenterX = fromNode.x + (fromNode.width ? fromNode.width / 2 : 70);\n        const fromCenterY = fromNode.y + 40;\n        const d0 = (p0.x - fromCenterX) ** 2 + (p0.y - fromCenterY) ** 2;\n        const d1 = (p1.x - fromCenterX) ** 2 + (p1.y - fromCenterY) ** 2;\n        if (d0 > d1) {\n          isReverse = true;\n        }\n      }\n      this.activeParticleReverse = isReverse;\n\n      const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');\n      group.setAttribute('class', 'threat-ball-group');\n\n      // Outer pulsating aura\n      const aura = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n      aura.setAttribute('r', '15');\n      aura.setAttribute('class', 'threat-ball-aura');\n      group.appendChild(aura);\n\n      // Middle glowing ring\n      const mid = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n      mid.setAttribute('r', '8');\n      mid.setAttribute('class', 'threat-ball-mid');\n      group.appendChild(mid);\n\n      // Core white-hot energy ball\n      const core = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n      core.setAttribute('r', '4.5');\n      core.setAttribute('class', 'threat-ball-core');\n      group.appendChild(core);\n\n      const startPt = isReverse ? p1 : p0;\n      group.setAttribute('transform', `translate(${startPt.x}, ${startPt.y})`);\n\n      simLayer.appendChild(group);\n      this.activeParticle = group;\n    } catch (e) {\n      console.warn(\"Could not initialize SVG particle:\", e);\n    }\n  }\n\n  removeParticle() {\n    if (this.activeParticle && this.activeParticle.parentNode) {\n      this.activeParticle.parentNode.removeChild(this.activeParticle);\n    }\n    this.activeParticle = null;\n    this.activeParticlePath = null;\n    this.activeParticleLen = 0;\n    this.activeParticleReverse = false;\n  }\n\n  // Concurrent Branch Particle helpers\n  spawnBranchParticle(branch, fromNodeId, toNodeId) {\n    this.removeBranchParticle(branch);\n    const simLayer = this.app.canvas ? this.app.canvas.simulationLayer : document.getElementById('simulationLayer');\n    if (!simLayer) return;\n\n    const pathEl = this.app.canvas ? this.app.canvas.findConnectionPath(fromNodeId, toNodeId) : null;\n    if (!pathEl) return;\n\n    try {\n      const len = pathEl.getTotalLength();\n      branch.pathLen = len;\n      branch.pathEl = pathEl;\n\n      const p0 = pathEl.getPointAtLength(0);\n      const p1 = pathEl.getPointAtLength(len);\n      const fromNode = this.app.workspace ? this.app.workspace.nodes.find(n => n.id === fromNodeId) : null;\n      let isReverse = false;\n      if (fromNode) {\n        const fromCenterX = fromNode.x + (fromNode.width ? fromNode.width / 2 : 70);\n        const fromCenterY = fromNode.y + 40;\n        const d0 = (p0.x - fromCenterX) ** 2 + (p0.y - fromCenterY) ** 2;\n        const d1 = (p1.x - fromCenterX) ** 2 + (p1.y - fromCenterY) ** 2;\n        if (d0 > d1) {\n          isReverse = true;\n        }\n      }\n      branch.isReverse = isReverse;\n\n      const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');\n      group.setAttribute('class', 'threat-ball-group');\n\n      const aura = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n      aura.setAttribute('r', '15');\n      aura.setAttribute('class', 'threat-ball-aura');\n      group.appendChild(aura);\n\n      const mid = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n      mid.setAttribute('r', '8');\n      mid.setAttribute('class', 'threat-ball-mid');\n      group.appendChild(mid);\n\n      const core = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n      core.setAttribute('r', '4.5');\n      core.setAttribute('class', 'threat-ball-core');\n      group.appendChild(core);\n\n      const startPt = isReverse ? p1 : p0;\n      group.setAttribute('transform', `translate(${startPt.x}, ${startPt.y})`);\n\n      simLayer.appendChild(group);\n      branch.particle = group;\n    } catch (e) {\n      console.warn(\"Could not spawn branch particle:\", e);\n    }\n  }\n\n  updateBranchParticlePosition(branch, fraction) {\n    if (!branch.particle || !branch.pathEl || branch.pathLen <= 0) return;\n    try {\n      const f = Math.min(1.0, Math.max(0.0, fraction));\n      const effF = branch.isReverse ? (1.0 - f) : f;\n      const dist = effF * branch.pathLen;\n      const pt = branch.pathEl.getPointAtLength(dist);\n      branch.particle.setAttribute('transform', `translate(${pt.x}, ${pt.y})`);\n    } catch (e) {}\n  }\n\n  removeBranchParticle(branch) {\n    if (branch.particle && branch.particle.parentNode) {\n      branch.particle.parentNode.removeChild(branch.particle);\n    }\n    branch.particle = null;\n    branch.pathEl = null;\n    branch.pathLen = 0;\n  }\n\n  removeAllParticles() {\n    this.removeParticle();\n    if (this.plan) {\n      this.plan.forEach(step => {\n        if (step.type === 'concurrent_outcome_branches' && step.branches) {\n          step.branches.forEach(b => this.removeBranchParticle(b));\n        }\n      });\n    }\n    const simLayer = this.app.canvas ? this.app.canvas.simulationLayer : document.getElementById('simulationLayer');\n    if (simLayer) {\n      simLayer.querySelectorAll('.threat-ball-group').forEach(el => el.remove());\n    }\n  }\n\n  // Trigger SVG expanding circular shockwave pulse at node entry point\n  triggerShockwave(node) {\n    const simLayer = this.app.canvas ? this.app.canvas.simulationLayer : document.getElementById('simulationLayer');\n    if (!simLayer || !node) return;\n\n    const cx = Math.round(node.x);\n    const cy = Math.round(node.y + 40);\n\n    const ring = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n    ring.setAttribute('cx', cx);\n    ring.setAttribute('cy', cy);\n    ring.setAttribute('class', 'sim-shockwave-pulse');\n    simLayer.appendChild(ring);\n\n    setTimeout(() => {\n      if (ring && ring.parentNode) ring.parentNode.removeChild(ring);\n    }, 700);\n  }\n\n  // Overlay dynamic status badge onto node card\n  setNodeBadge(cardEl, text, className) {\n    if (!cardEl) return;\n    cardEl.classList.add('has-sim-badge');\n    let badge = cardEl.querySelector('.sim-card-badge');\n    if (!badge) {\n      badge = document.createElement('span');\n      badge.className = `sim-card-badge ${className}`;\n      cardEl.appendChild(badge);\n    } else {\n      badge.className = `sim-card-badge ${className}`;\n    }\n    badge.textContent = text;\n  }\n\n  clearVisualStates() {\n    const allBadges = document.querySelectorAll('.sim-card-badge');\n    allBadges.forEach(b => b.remove());\n\n    const allNodes = document.querySelectorAll('.bowtie-node');\n    allNodes.forEach(n => {\n      n.classList.remove(\n        'has-sim-badge',\n        'is-sim-active-threat',\n        'is-sim-challenged',\n        'is-sim-impacted',\n        'is-sim-holding',\n        'is-sim-outcome-holding',\n        'is-sim-outcome-barrier-active',\n        'is-sim-escalation-pulsing',\n        'is-sim-mitigation-active',\n        'is-sim-breached',\n        'is-sim-contained',\n        'is-sim-topevent-alarm'\n      );\n    });\n\n    const simLayer = this.app.canvas ? this.app.canvas.simulationLayer : document.getElementById('simulationLayer');\n    if (simLayer) {\n      simLayer.innerHTML = '';\n    }\n  }\n\n  completeSimulation(isContained = false) {\n    this.isRunning = false;\n    this.isPaused = false;\n    if (this.animFrameId) {\n      cancelAnimationFrame(this.animFrameId);\n      this.animFrameId = null;\n    }\n    this.removeParticle();\n    this.updateProgressBar(1.0);\n\n    const timeStr = this.formatTime(this.elapsedSimSec);\n    const thName = this.getThreatTitle();\n    if (isContained) {\n      this.updateTransportUI('contained');\n      if (this.metricStage) this.metricStage.textContent = 'Scenario Contained ✅';\n      this.logEvent(`[${timeStr}] 🏁 SITUATION RUNNER CONCLUDED: ${thName} successfully arrested by defensive barriers!`, 'contained');\n    } else {\n      this.updateTransportUI('breached');\n      if (this.metricStage) this.metricStage.textContent = 'Incident Breached 🚨';\n      this.logEvent(`[${timeStr}] 🏁 SITUATION RUNNER CONCLUDED: ${thName} propagated through barriers to ultimate loss consequences.`, 'top-event');\n    }\n  }\n\n  updateTransportUI(state) {\n    if (this.statusPill) {\n      this.statusPill.className = `sim-status-pill ${state}`;\n      this.statusPill.textContent = state.toUpperCase();\n    }\n\n    if (this.btnPlay) {\n      const fullSpan = this.btnPlay.querySelector('.btn-text-full');\n      const shortSpan = this.btnPlay.querySelector('.btn-text-short');\n      const fallbackSpan = (!fullSpan && !shortSpan) ? this.btnPlay.querySelector('span') : null;\n\n      if (state === 'running') {\n        this.btnPlay.disabled = true;\n        if (fullSpan) fullSpan.textContent = 'Running...';\n        if (shortSpan) shortSpan.textContent = 'Running';\n        if (fallbackSpan) fallbackSpan.textContent = 'Running...';\n      } else if (state === 'paused') {\n        this.btnPlay.disabled = false;\n        if (fullSpan) fullSpan.textContent = 'Resume';\n        if (shortSpan) shortSpan.textContent = 'Resume';\n        if (fallbackSpan) fallbackSpan.textContent = 'Resume';\n      } else {\n        this.btnPlay.disabled = false;\n        if (fullSpan) fullSpan.textContent = 'Run Situation';\n        if (shortSpan) shortSpan.textContent = 'Run';\n        if (fallbackSpan) fallbackSpan.textContent = 'Run Situation';\n      }\n    }\n\n    if (this.btnPause) {\n      this.btnPause.disabled = (state !== 'running');\n    }\n  }\n\n  updateClocks(simSec, realSec) {\n    if (this.clockDisplay) {\n      this.clockDisplay.textContent = this.formatTime(simSec);\n    }\n    if (this.metricSim) {\n      this.metricSim.textContent = `${simSec.toFixed(1)}s / ${this.targetSimDuration.toFixed(1)}s`;\n    }\n    if (this.metricReal) {\n      this.metricReal.textContent = `${realSec.toFixed(1)}s / ${this.totalPlanRealSec.toFixed(1)}s`;\n    }\n  }\n\n  updateProgressBar(fraction) {\n    if (this.progressFill) {\n      const pct = Math.min(100, Math.max(0, fraction * 100));\n      this.progressFill.style.width = `${pct.toFixed(1)}%`;\n    }\n  }\n\n  formatTime(seconds) {\n    const s = Math.max(0, seconds);\n    const hrs = Math.floor(s / 3600);\n    const mins = Math.floor((s % 3600) / 60);\n    const secs = (s % 60).toFixed(1);\n    const minStr = mins < 10 ? `0${mins}` : `${mins}`;\n    const secStr = (parseFloat(secs) < 10 ? `0${secs}` : `${secs}`);\n    if (hrs > 0) {\n      const hrStr = hrs < 10 ? `0${hrs}` : `${hrs}`;\n      return `${hrStr}:${minStr}:${secStr}`;\n    }\n    return `${minStr}:${secStr}`;\n  }\n\n  logEvent(text, type = 'info') {\n    const entry = {\n      time: this.formatTime(this.elapsedSimSec),\n      text: text,\n      type: type,\n    };\n    this.eventLogs.push(entry);\n\n    if (this.logCountBadge) {\n      this.logCountBadge.textContent = this.eventLogs.length;\n    }\n\n    if (this.logList) {\n      const emptyMsg = this.logList.querySelector('.sim-log-empty');\n      if (emptyMsg) emptyMsg.remove();\n\n      const item = document.createElement('div');\n      item.className = `sim-log-item log-${type}`;\n      item.innerHTML = `\n        <span class=\"sim-log-time\">${entry.time}</span>\n        <span class=\"sim-log-msg\">${this.escapeHtml(text)}</span>\n      `;\n      this.logList.appendChild(item);\n      this.logList.scrollTop = this.logList.scrollHeight;\n    }\n  }\n\n  clearLog() {\n    this.eventLogs = [];\n    if (this.logCountBadge) this.logCountBadge.textContent = '0';\n    if (this.logList) {\n      this.logList.innerHTML = '<div class=\"sim-log-empty\">Log cleared. Ready for next incident scenario.</div>';\n    }\n  }\n\n  copyLogToClipboard() {\n    const fullText = this.eventLogs.map(e => `${e.time} - ${e.text}`).join('\\n');\n    navigator.clipboard.writeText(fullText).then(() => {\n      this.app.showNotification('Incident chronology copied to clipboard!', 'success');\n    }).catch(() => {\n      alert(fullText);\n    });\n  }\n\n  escapeHtml(str) {\n    if (!str) return '';\n    return str\n      .replace(/&/g, '&amp;')\n      .replace(/</g, '&lt;')\n      .replace(/>/g, '&gt;')\n      .replace(/\"/g, '&quot;')\n      .replace(/'/g, '&#039;');\n  }\n}\n",
    "js/tree.js": "// Left-hand Pane Tree View Component & Global Element Library\nclass BowtieTree {\n  constructor(treeContainerEl, libraryContainerEl, app) {\n    this.treeContainer = treeContainerEl;\n    this.libraryContainer = libraryContainerEl;\n    this.app = app;\n    this.collapsedNodes = new Set();\n    this.searchQuery = '';\n\n    this.initSearch();\n  }\n\n  initSearch() {\n    const searchInput = document.getElementById('treeSearchInput');\n    if (searchInput) {\n      searchInput.addEventListener('input', (e) => {\n        this.searchQuery = e.target.value.toLowerCase().trim();\n        this.render();\n      });\n    }\n  }\n\n  render() {\n    this.renderTree();\n    this.renderLibrary();\n  }\n\n  renderTree() {\n    this.treeContainer.innerHTML = '';\n    const workspace = this.app ? this.app.workspace : null;\n    if (!workspace || !workspace.diagrams) return;\n\n    const currentDiag = (this.app && typeof this.app.getCurrentDiagram === 'function')\n      ? this.app.getCurrentDiagram()\n      : ((this.app && typeof this.app.getActiveDiagram === 'function')\n          ? this.app.getActiveDiagram()\n          : (this.app ? this.app.currentDiagram : null));\n\n    if (!currentDiag) {\n      this.treeContainer.innerHTML = '<div class=\"text-xs text-muted text-center\" style=\"padding: 20px 0;\">No active diagram.</div>';\n      return;\n    }\n\n    const diagNodes = (workspace.nodes || []).filter(n => n.diagram_id === currentDiag.id);\n    if (diagNodes.length === 0) {\n      this.treeContainer.innerHTML = '<div class=\"text-xs text-muted text-center\" style=\"padding: 20px 0;\">No elements in this diagram.</div>';\n      return;\n    }\n\n    const customColors = this.app && typeof this.app.getCustomColors === 'function' ? this.app.getCustomColors() : {};\n    const customNames = this.app && typeof this.app.getCustomNames === 'function' ? this.app.getCustomNames() : {};\n\n    // Find all root nodes (hazards and unparented nodes) for the current diagram\n    const roots = diagNodes.filter(n => !n.parent_id || !diagNodes.some(p => p.id === n.parent_id));\n    // Sort multiple hazards/roots vertically by their canvas Y position\n    roots.sort((a, b) => (a.y || 0) - (b.y || 0));\n\n    roots.forEach(root => {\n      this.buildTreeNode(root, diagNodes, this.treeContainer, customColors, customNames);\n    });\n\n    if (this.searchQuery && this.treeContainer && this.treeContainer.children && this.treeContainer.children.length > 0) {\n      const allHidden = Array.from(this.treeContainer.children).every(el => el.style && el.style.display === 'none');\n      if (allHidden) {\n        this.treeContainer.innerHTML = '<div class=\"text-xs text-muted text-center\" style=\"padding: 20px 0;\">No matching elements found.</div>';\n      }\n    }\n  }\n\n  getNodeTreeChildren(node, allNodes) {\n    if (!node) return [];\n    if (node.node_type === 'top_event') {\n      // Under top_event, children are Threats and Outcomes (not loose outcome barriers)\n      const children = allNodes.filter(n => (n.node_type === 'threat' || n.node_type === 'outcome') && (n.parent_id === node.id || !n.parent_id));\n      children.sort((a, b) => (a.y || 0) - (b.y || 0));\n      return children;\n    } else if (node.node_type === 'threat') {\n      // Under threat, show all threat barriers belonging to this threat\n      const children = allNodes.filter(n => n.node_type === 'threat_barrier' && (n.parent_id === node.id || this.getOwningThreatId(n, allNodes) === node.id));\n      children.sort((a, b) => (a.x || 0) - (b.x || 0));\n      return children;\n    } else if (node.node_type === 'outcome') {\n      // Outcome hierarchy: outcome > barriers > escalation factor > mitigation\n      const children = allNodes.filter(n => n.node_type === 'outcome_barrier' && (n.parent_id === node.id || this.getOwningOutcomeId(n, allNodes) === node.id));\n      children.sort((a, b) => (a.x || 0) - (b.x || 0));\n      return children;\n    } else if (node.node_type === 'threat_barrier' || node.node_type === 'outcome_barrier') {\n      // Under a barrier, children are its escalation factors ONLY\n      const children = allNodes.filter(n => n.parent_id === node.id && n.node_type === 'escalation_factor');\n      children.sort((a, b) => (a.y || 0) - (b.y || 0));\n      return children;\n    } else if (node.node_type === 'escalation_factor') {\n      // Under an escalation factor, children are its mitigations\n      const children = allNodes.filter(n => n.parent_id === node.id && n.node_type === 'mitigation');\n      children.sort((a, b) => (a.y || 0) - (b.y || 0));\n      return children;\n    } else {\n      const children = allNodes.filter(n => n.parent_id === node.id);\n      children.sort((a, b) => (a.y || 0) - (b.y || 0));\n      return children;\n    }\n  }\n\n  getOwningOutcomeId(barrier, allNodes) {\n    if (!barrier || barrier.node_type !== 'outcome_barrier') return null;\n    let curr = barrier;\n    const seen = new Set();\n    while (curr && !seen.has(curr.id)) {\n      seen.add(curr.id);\n      if (curr.parent_id) {\n        const parent = allNodes.find(n => n.id === curr.parent_id);\n        if (parent) {\n          if (parent.node_type === 'outcome') return parent.id;\n          if (parent.node_type === 'outcome_barrier') {\n            curr = parent;\n            continue;\n          }\n        }\n      }\n      break;\n    }\n    const childOc = allNodes.find(n => n.node_type === 'outcome' && n.parent_id === barrier.id);\n    if (childOc) return childOc;\n    const outcomes = allNodes.filter(n => n.node_type === 'outcome');\n    if (outcomes.length === 0) return null;\n    const closest = outcomes.reduce((best, oc) => {\n      const dBest = Math.abs((barrier.y || 0) - (best.y || 0));\n      const dCurr = Math.abs((barrier.y || 0) - (oc.y || 0));\n      return dCurr < dBest ? oc : best;\n    }, outcomes[0]);\n    return closest ? closest.id : null;\n  }\n\n  getOwningThreatId(barrier, allNodes) {\n    if (!barrier || barrier.node_type !== 'threat_barrier') return null;\n    let curr = barrier;\n    const seen = new Set();\n    while (curr && !seen.has(curr.id)) {\n      seen.add(curr.id);\n      if (curr.parent_id) {\n        const parent = allNodes.find(n => n.id === curr.parent_id);\n        if (parent) {\n          if (parent.node_type === 'threat') return parent.id;\n          if (parent.node_type === 'threat_barrier') {\n            curr = parent;\n            continue;\n          }\n        }\n      }\n      break;\n    }\n    const threats = allNodes.filter(n => n.node_type === 'threat');\n    if (threats.length === 0) return null;\n    const closest = threats.reduce((best, t) => {\n      const dBest = Math.abs((barrier.y || 0) - (t.y || 0));\n      const dCurr = Math.abs((barrier.y || 0) - (t.y || 0));\n      return dCurr < dBest ? t : best;\n    }, threats[0]);\n    return closest ? closest.id : null;\n  }\n\n  buildTreeNode(node, allNodes, container, customColors, customNames) {\n    // If search active, check match\n    const typeLabel = this.app ? this.app.getNodeDisplayTitle(node) : (customNames[node.node_type] || node.title);\n    const match = !this.searchQuery || \n                  (node.description || '').toLowerCase().includes(this.searchQuery) ||\n                  typeLabel.toLowerCase().includes(this.searchQuery);\n\n    const children = this.getNodeTreeChildren(node, allNodes);\n    // Sort children vertically by canvas Y position\n    children.sort((a, b) => (a.y || 0) - (b.y || 0));\n    const hasChildren = children.length > 0;\n\n    const itemEl = document.createElement('div');\n    itemEl.className = 'tree-item';\n\n    const activeNodeId = (this.app && this.app.canvas) ? this.app.canvas.activeNodeId : null;\n    const rowEl = document.createElement('div');\n    rowEl.className = `tree-node-row ${node.id === activeNodeId ? 'active' : ''}`;\n    if (!match && !this.searchQueryHasChild(children, allNodes)) {\n      itemEl.style.display = 'none';\n    }\n\n    const dotColor = customColors[node.node_type] || '#64748b';\n    const isCollapsed = this.collapsedNodes.has(node.id) && !this.searchQuery;\n\n    const escape = (text) => (this.app && typeof this.app.escapeHtml === 'function') ? this.app.escapeHtml(text) : (text || '');\n    const descText = node.description\n      ? escape(node.description)\n      : `<span style=\"color: #94a3b8; font-style: italic;\">(${this.app && this.app.canvas ? this.app.canvas.getNodeSuggestedDescription(node) : typeLabel})</span>`;\n\n    rowEl.innerHTML = `\n      <span class=\"tree-toggle\" data-toggle=\"${node.id}\">\n        ${hasChildren ? (isCollapsed ? '►' : '▼') : '•'}\n      </span>\n      <span class=\"tree-dot\" style=\"background-color: ${dotColor};\"></span>\n      <span class=\"tree-label\" title=\"${escape(node.description || typeLabel)}\">\n        <strong>${escape(typeLabel)}:</strong> ${descText}\n      </span>\n    `;\n\n    if (hasChildren) {\n      const toggleEl = rowEl.querySelector('.tree-toggle');\n      if (toggleEl) {\n        toggleEl.addEventListener('click', (e) => {\n          e.stopPropagation();\n          this.toggleCollapse(node.id);\n        });\n      }\n    }\n\n    rowEl.addEventListener('click', () => {\n      if (this.app && this.app.canvas) {\n        this.app.canvas.setActiveNode(node.id);\n        this.app.canvas.centerOnNode(node.id);\n      }\n      this.highlightActive(node.id);\n      if (window.innerWidth <= 900 && this.app && typeof this.app.closeMobileDrawers === 'function') {\n        this.app.closeMobileDrawers();\n      }\n    });\n\n    itemEl.appendChild(rowEl);\n\n    if (hasChildren && !isCollapsed) {\n      const childWrap = document.createElement('div');\n      childWrap.className = 'tree-children';\n      children.forEach(child => {\n        this.buildTreeNode(child, allNodes, childWrap, customColors, customNames);\n      });\n      itemEl.appendChild(childWrap);\n    }\n\n    container.appendChild(itemEl);\n  }\n\n  searchQueryHasChild(children, allNodes) {\n    if (!this.searchQuery) return true;\n    for (const c of children) {\n      const typeLabel = this.app ? this.app.getNodeDisplayTitle(c) : (c.title || '');\n      if ((c.description || '').toLowerCase().includes(this.searchQuery) || typeLabel.toLowerCase().includes(this.searchQuery)) return true;\n      const subChildren = this.getNodeTreeChildren(c, allNodes);\n      if (this.searchQueryHasChild(subChildren, allNodes)) return true;\n    }\n    return false;\n  }\n\n  toggleCollapse(id) {\n    if (this.collapsedNodes.has(id)) {\n      this.collapsedNodes.delete(id);\n    } else {\n      this.collapsedNodes.add(id);\n    }\n    this.renderTree();\n  }\n\n  highlightActive(nodeId) {\n    document.querySelectorAll('.tree-node-row').forEach(row => {\n      row.classList.remove('active');\n    });\n    // Re-render or highlight\n    const rows = document.querySelectorAll('.tree-node-row');\n    rows.forEach(r => {\n      if (r.querySelector(`[data-toggle=\"${nodeId}\"]`)) {\n        r.classList.add('active');\n      }\n    });\n  }\n\n  renderLibrary() {\n    this.libraryContainer.innerHTML = '';\n    const globals = this.app.workspace.global_elements || [];\n\n    if (globals.length === 0) {\n      this.libraryContainer.innerHTML = `<div class=\"text-xs text-muted text-center\" style=\"padding: 20px 0;\">No master elements created yet.</div>`;\n      return;\n    }\n\n    const customColors = this.app.getCustomColors();\n\n    globals.forEach(ge => {\n      const card = document.createElement('div');\n      card.className = 'global-elem-card';\n\n      // Count instances across diagrams\n      const instances = this.app.workspace.nodes.filter(n => n.global_element_id === ge.id);\n      const dotColor = customColors[ge.node_type] || '#334155';\n\n      const safeDotColor = this.app.escapeHtml(dotColor);\n      const safeName = this.app.escapeHtml(ge.name || '');\n      const safeRole = this.app.escapeHtml(ge.responsible_role || 'Unassigned');\n      const safeRating = this.app.escapeHtml(ge.effectiveness || 'N/A');\n      const safeDesc = this.app.escapeHtml((ge.description || '').substring(0, 60));\n\n      card.innerHTML = `\n        <div class=\"global-elem-card-title\">\n          <span><span class=\"tree-dot\" style=\"display:inline-block; background-color:${safeDotColor}; margin-right:6px;\"></span>${safeName}</span>\n          <span class=\"badge\" title=\"${instances.length} instances on diagrams\">${instances.length}x</span>\n        </div>\n        <div class=\"global-elem-card-meta\">\n          <div>Role: ${safeRole} | Rating: ${safeRating}</div>\n          <div style=\"margin-top: 4px; color: #475569; font-style: italic;\">\"${safeDesc}...\"</div>\n        </div>\n        <div style=\"margin-top: 8px; display: flex; gap: 6px;\">\n          <button class=\"btn btn-xs btn-outline btn-insert-ge\" style=\"color: #0f172a;\">+ Insert into Diagram</button>\n        </div>\n      `;\n\n      card.querySelector('.btn-insert-ge').addEventListener('click', (e) => {\n        e.stopPropagation();\n        this.app.insertGlobalElementInstance(ge);\n      });\n\n      this.libraryContainer.appendChild(card);\n    });\n  }\n}\n"
  },
  "sha256": "0c2fccf247a7ad02c18ca9b8b15b3c1fd7e1584d43754f1fccd4a3fe88247a6f",
  "signature": "180bae90e559c4809ffb18c8f89f9fc1325ff9d74f96af28d363c62a939b306c"
}